diff --git a/CREDITS.md b/CREDITS.md index 238a4d8b..782f40a8 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,53 +1,58 @@ # 🔃 Dependencies -PageTop is developed using the [Rust programming language](https://www.rust-lang.org/) and stands on -the shoulders of giants, leveraging some of the most stable and renowned libraries (*crates*) from -the [Rust ecosystem](https://lib.rs), including: +PageTop is developed in the [Rust programming language](https://www.rust-lang.org/) and stands on +the shoulders of true giants, using some of the most stable and renowned libraries (*crates*) from +the [Rust ecosystem](https://lib.rs), such as: + +* [Actix Web](https://actix.rs/) for web services and server management. +* [Tracing](https://github.com/tokio-rs/tracing) for the diagnostic system and structured logging. +* [Fluent templates](https://github.com/XAMPPRocky/fluent-templates) that incorporate + [Fluent](https://projectfluent.org/) for project internationalization. +* Among others, which you can review in the PageTop + [`Cargo.toml`](https://github.com/manuelcillero/pagetop/blob/main/Cargo.toml) file. - * [Actix Web](https://actix.rs/) for web services and server management. - * [Tracing](https://github.com/tokio-rs/tracing) for diagnostics and structured logging. - * [Fluent templates](https://github.com/XAMPPRocky/fluent-templates), which integrate - [Fluent](https://projectfluent.org/) for internationalization. - * Additional crates, which you can explore in the `Cargo.toml` files of PageTop and its packages. # ⌨️ Code -PageTop incorporates code from several well-regarded crates to enhance its functionality: +PageTop integrates code from various renowned crates to enhance functionality: - * **[Config (v0.11.0)](https://github.com/mehcode/config-rs/tree/0.11.0)**: Includes code from - [config-rs](https://crates.io/crates/config) by [Ryan Leckey](https://crates.io/users/mehcode), - chosen for its advantages in reading configuration settings and delegating assignment to safe - types, tailored to the specific needs of each package, theme, or application. +* [**Config (v0.11.0)**](https://github.com/mehcode/config-rs/tree/0.11.0): Includes code from + [config-rs](https://crates.io/crates/config) by [Ryan Leckey](https://crates.io/users/mehcode), + chosen for its advantages in reading configuration settings and delegating assignment to safe + types, tailored to the specific needs of each package, theme, or application. - * **[Maud (v0.25.0)](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud)**: An adapted version - of the excellent [maud](https://crates.io/crates/maud) crate by - [Chris Wong](https://crates.io/users/lambda-fairy) is integrated, enabling its functionalities - without requiring a direct dependency in the `Cargo.toml` files. +* [**Maud (v0.25.0)**](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud): An adapted version + of the excellent [maud](https://crates.io/crates/maud) crate by + [Chris Wong](https://crates.io/users/lambda-fairy) is incorporated to leverage its functionalities without requiring a reference to `maud` in the `Cargo.toml` files. + +* **SmartDefault (v0.7.1)**: Embedded [SmartDefault](https://crates.io/crates/smart_default) by + [Jane Doe](https://crates.io/users/jane-doe) as `AutoDefault`to simplify the documentation of + Default implementations and also removes the need to explicitly list `smart_default` in the + `Cargo.toml` files. - * **SmartDefault (v0.7.1)**: The [SmartDefault](https://crates.io/crates/smart_default) crate by - [Jane Doe](https://crates.io/users/jane-doe) has been embedded as `AutoDefault`, simplifying - `Default` implementations and eliminating the need to explicitly reference `smart_default` in - the `Cargo.toml` files. # 🗚 FIGfonts PageTop uses the [figlet-rs](https://crates.io/crates/figlet-rs) package by *yuanbohan* to display a -presentation banner in the terminal featuring the application's name in -[FIGlet](http://www.figlet.org) characters. The fonts included in `pagetop/src/app` are: +presentation banner in the terminal with the application's name using +[FIGlet](http://www.figlet.org) characters. The fonts included in `src/app` are: * [slant.flf](http://www.figlet.org/fontdb_example.cgi?font=slant.flf) by *Glenn Chappell* * [small.flf](http://www.figlet.org/fontdb_example.cgi?font=small.flf) by *Glenn Chappell* (default) * [speed.flf](http://www.figlet.org/fontdb_example.cgi?font=speed.flf) by *Claude Martins* * [starwars.flf](http://www.figlet.org/fontdb_example.cgi?font=starwars.flf) by *Ryan Youck* + # 📰 Templates -The default welcome homepage design is inspired by a tutorial for creating a unique -[Neobrutalism](https://www.codewithfaraz.com/content/109/creating-a-unique-neobrutalism-portfolio-page-with-html-css-and-javascript) -portfolio page by [Faraz](https://www.codewithfaraz.com/). +* The default welcome homepage design is based on the + [Zinc](https://themewagon.com/themes/free-bootstrap-5-html5-business-website-template-zinc) + template created by [inovatik](https://inovatik.com/) and distributed by + [ThemeWagon](https://themewagon.com). + # 🎨 Icon -"The Creature" smiling is a playful creation by [Webalys](https://www.iconfinder.com/webalys). It is -part of their [Nasty Icons](https://www.iconfinder.com/iconsets/nasty) collection, available on +"The creature" smiling is a fun creation by [Webalys](https://www.iconfinder.com/webalys). It can be +found in their [Nasty Icons](https://www.iconfinder.com/iconsets/nasty) collection available on [ICONFINDER](https://www.iconfinder.com). diff --git a/Cargo.lock b/Cargo.lock index a668ac4f..5eca0cae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.6.0", + "bitflags", "bytes", "futures-core", "futures-sink", @@ -29,7 +29,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web", - "bitflags 2.6.0", + "bitflags", "bytes", "derive_more 0.99.18", "futures-core", @@ -54,7 +54,7 @@ dependencies = [ "actix-utils", "ahash", "base64 0.22.1", - "bitflags 2.6.0", + "bitflags", "brotli", "bytes", "bytestring", @@ -128,7 +128,7 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2 0.5.7", + "socket2", "tokio", "tracing", ] @@ -208,7 +208,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.5.7", + "socket2", "time", "url", ] @@ -309,12 +309,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -406,196 +400,6 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand 2.2.0", - "futures-lite 2.5.0", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.3.1", - "async-executor", - "async-io 2.4.0", - "async-lock 3.4.0", - "blocking", - "futures-lite 2.5.0", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" -dependencies = [ - "async-lock 3.4.0", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite 2.5.0", - "parking", - "polling 3.7.4", - "rustix 0.38.40", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener 5.3.1", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-std" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io 2.4.0", - "async-lock 3.4.0", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 2.5.0", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" version = "1.4.0" @@ -629,26 +433,11 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -dependencies = [ - "serde", -] [[package]] name = "block-buffer" @@ -659,19 +448,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel 2.3.1", - "async-task", - "futures-io", - "futures-lite 2.5.0", - "piper", -] - [[package]] name = "brotli" version = "6.0.0" @@ -856,21 +632,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7439becb5fafc780b6f4de382b1a7a3e70234afe783854a4702ee8adbb838609" -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "convert_case" version = "0.4.0" @@ -895,16 +656,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -920,21 +671,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - [[package]] name = "crc32fast" version = "1.4.2" @@ -972,15 +708,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -1007,51 +734,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "darling" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "darling_macro" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "der" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - [[package]] name = "deranged" version = "0.3.11" @@ -1108,7 +790,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", "subtle", ] @@ -1124,12 +805,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "drust" version = "0.0.3" @@ -1138,15 +813,6 @@ dependencies = [ "pagetop-bootsier", ] -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -dependencies = [ - "serde", -] - [[package]] name = "encoding_rs" version = "0.8.35" @@ -1172,59 +838,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener 5.3.1", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" - [[package]] name = "figlet-rs" version = "0.1.5" @@ -1315,8 +928,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ - "futures-core", - "futures-sink", "spin", ] @@ -1326,21 +937,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1350,104 +946,12 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" -dependencies = [ - "fastrand 2.2.0", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "futures-sink" version = "0.3.31" @@ -1466,13 +970,8 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", - "futures-macro", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1542,23 +1041,11 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.6.0", + "bitflags", "ignore", "walkdir", ] -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "grass" version = "0.13.4" @@ -1619,45 +1106,12 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown 0.14.5", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hkdf" version = "0.12.4" @@ -1676,15 +1130,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "http" version = "0.2.12" @@ -1864,12 +1309,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.0.3" @@ -1942,17 +1381,6 @@ dependencies = [ "hashbrown 0.15.1", ] -[[package]] -name = "inherent" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "inout" version = "0.1.3" @@ -1962,15 +1390,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "intl-memoizer" version = "0.5.2" @@ -1990,32 +1409,12 @@ dependencies = [ "unic-langid", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.11" @@ -2040,15 +1439,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -2069,9 +1459,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -2085,23 +1472,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -2146,9 +1516,6 @@ name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -dependencies = [ - "value-bag", -] [[package]] name = "matchers" @@ -2159,16 +1526,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - [[package]] name = "memchr" version = "2.7.4" @@ -2212,7 +1569,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", "log", "wasi", @@ -2225,23 +1582,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577" -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nom" version = "7.1.3" @@ -2262,49 +1602,12 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", -] - [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2312,7 +1615,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -2336,84 +1638,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "3.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" -dependencies = [ - "num-traits", -] - -[[package]] -name = "ouroboros" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67" -dependencies = [ - "aliasable", - "ouroboros_macro", - "static_assertions", -] - -[[package]] -name = "ouroboros_macro" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" -dependencies = [ - "heck 0.4.1", - "itertools", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.87", -] - [[package]] name = "overload" version = "0.1.1" @@ -2435,7 +1659,6 @@ dependencies = [ "fluent-templates", "itoa", "nom", - "pagetop-build", "pagetop-macros", "paste", "serde", @@ -2490,26 +1713,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "pagetop-seaorm" -version = "0.0.1" -dependencies = [ - "async-trait", - "futures", - "pagetop", - "sea-orm", - "sea-schema", - "serde", - "static-files", - "url", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - [[package]] name = "parking_lot" version = "0.12.3" @@ -2563,15 +1766,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696" -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -2707,75 +1901,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand 2.2.0", - "futures-io", -] - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.4.0", - "pin-project-lite", - "rustix 0.38.40", - "tracing", - "windows-sys 0.59.0", -] - [[package]] name = "polyval" version = "0.6.2" @@ -2836,28 +1967,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -2873,19 +1982,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", - "version_check", - "yansi", -] - [[package]] name = "quote" version = "1.0.37" @@ -2931,7 +2027,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.6.0", + "bitflags", ] [[package]] @@ -2984,26 +2080,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "rsa" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3025,30 +2101,16 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.37.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustix" version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", - "linux-raw-sys 0.4.14", + "linux-raw-sys", "windows-sys 0.52.0", ] @@ -3067,151 +2129,12 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sea-bae" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" -dependencies = [ - "heck 0.4.1", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "sea-orm" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5680a8b686985116607ef5f5af2b1f9e1cc2c228330e93101816a0baa279afa" -dependencies = [ - "async-stream", - "async-trait", - "futures", - "log", - "ouroboros", - "sea-orm-macros", - "sea-query", - "sea-query-binder", - "serde", - "sqlx", - "strum", - "thiserror", - "tracing", - "url", -] - -[[package]] -name = "sea-orm-macros" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a239e3bb1b566ad4ec2654d0d193d6ceddfd733487edc9c21a64d214c773910" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "sea-bae", - "syn 2.0.87", - "unicode-ident", -] - -[[package]] -name = "sea-query" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff504d13b5e4b52fffcf2fb203d0352a5722fa5151696db768933e41e1e591bb" -dependencies = [ - "inherent", - "ordered-float", - "sea-query-derive", -] - -[[package]] -name = "sea-query-binder" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" -dependencies = [ - "sea-query", - "sqlx", -] - -[[package]] -name = "sea-query-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9834af2c4bd8c5162f00c89f1701fb6886119a88062cf76fe842ea9e232b9839" -dependencies = [ - "darling", - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 2.0.87", - "thiserror", -] - -[[package]] -name = "sea-schema" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab1592d17860a9a8584d9b549aebcd06f7bdc3ff615f71752486ba0b05b1e6e" -dependencies = [ - "futures", - "sea-query", - "sea-schema-derive", -] - -[[package]] -name = "sea-schema-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "self_cell" version = "0.10.3" @@ -3332,16 +2255,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core", -] - [[package]] name = "siphasher" version = "0.3.11" @@ -3372,19 +2285,6 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] [[package]] name = "socket2" @@ -3405,221 +2305,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlformat" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" -dependencies = [ - "nom", - "unicode_categories", -] - -[[package]] -name = "sqlx" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" -dependencies = [ - "async-io 1.13.0", - "async-std", - "atoi", - "byteorder", - "bytes", - "crc", - "crossbeam-queue", - "either", - "event-listener 5.3.1", - "futures-channel", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.14.5", - "hashlink", - "hex", - "indexmap", - "log", - "memchr", - "native-tls", - "once_cell", - "paste", - "percent-encoding", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlformat", - "thiserror", - "tracing", - "url", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.87", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" -dependencies = [ - "async-std", - "dotenvy", - "either", - "heck 0.5.0", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.87", - "tempfile", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.6.0", - "byteorder", - "bytes", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" -dependencies = [ - "atoi", - "base64 0.22.1", - "bitflags 2.6.0", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "tracing", - "url", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3637,35 +2322,12 @@ dependencies = [ "path-slash", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" - [[package]] name = "substring" version = "1.4.5" @@ -3713,19 +2375,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "tempfile" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" -dependencies = [ - "cfg-if", - "fastrand 2.2.0", - "once_cell", - "rustix 0.38.40", - "windows-sys 0.59.0", -] - [[package]] name = "tera" version = "1.20.0" @@ -3754,7 +2403,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ - "rustix 0.38.40", + "rustix", "windows-sys 0.59.0", ] @@ -3829,21 +2478,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" version = "1.41.1" @@ -3857,7 +2491,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2", "windows-sys 0.52.0", ] @@ -4128,45 +2762,18 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" - [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - [[package]] name = "universal-hash" version = "0.5.1" @@ -4179,9 +2786,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.4" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", "idna", @@ -4227,30 +2834,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "value-bag" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "walkdir" version = "2.5.0" @@ -4267,12 +2856,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.95" @@ -4299,18 +2882,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.95" @@ -4340,26 +2911,6 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" -[[package]] -name = "web-sys" -version = "0.3.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "whoami" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" -dependencies = [ - "redox_syscall", - "wasite", -] - [[package]] name = "winapi" version = "0.3.9" @@ -4569,12 +3120,6 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - [[package]] name = "yoke" version = "0.7.4" @@ -4641,12 +3186,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - [[package]] name = "zerovec" version = "0.10.4" diff --git a/Cargo.toml b/Cargo.toml index e37f712e..effe459c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,16 +5,18 @@ members = [ "helpers/pagetop-build", "helpers/pagetop-macros", - # PageTop - "pagetop", - # Packages + "packages/pagetop", "packages/pagetop-aliner", "packages/pagetop-bootsier", - "packages/pagetop-seaorm", # App - "drust", + "packages/drust", + + # Examples +# "examples/app-basic", +# "examples/hello-world", +# "examples/hello-name", ] [workspace.package] @@ -32,9 +34,7 @@ static-files = "0.2.4" pagetop-build = { version = "0.0", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" } -# PageTop -pagetop = { version = "0.0", path = "pagetop" } - # Packages +pagetop = { version = "0.0", path = "packages/pagetop" } pagetop-aliner = { version = "0.0", path = "packages/pagetop-aliner" } pagetop-bootsier = { version = "0.0", path = "packages/pagetop-bootsier" } diff --git a/README.md b/README.md index 96049b9d..df5b2429 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ impl PackageTrait for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage { Page::new(request) - .with_body(PrepareMarkup::With(html! { h1 { "Hello World!" } })) + .with_component(Html::with(html! { h1 { "Hello World!" } })) .render() } diff --git a/pagetop/build.rs b/build.rs similarity index 53% rename from pagetop/build.rs rename to build.rs index 1450422c..6e9dc40b 100644 --- a/pagetop/build.rs +++ b/build.rs @@ -1,7 +1,7 @@ use pagetop_build::StaticFilesBundle; fn main() -> std::io::Result<()> { - StaticFilesBundle::from_dir("../static", None) - .with_name("assets") + StaticFilesBundle::from_dir("./static/base") + .with_name("base") .build() } diff --git a/config/common.toml b/config/common.toml index 2b018a16..900872d6 100644 --- a/config/common.toml +++ b/config/common.toml @@ -1,6 +1,5 @@ [app] -name = "Drust" -description = "A modern web Content Management System to share your world." +name = "Samples" -[database] -db_type = "mysql" +[log] +tracing = "Debug" diff --git a/pagetop/examples/app-basic.rs b/examples/app-basic.rs similarity index 100% rename from pagetop/examples/app-basic.rs rename to examples/app-basic.rs diff --git a/pagetop/examples/hello-name.rs b/examples/hello-name.rs similarity index 87% rename from pagetop/examples/hello-name.rs rename to examples/hello-name.rs index 8dba5cec..3a03e8b1 100644 --- a/pagetop/examples/hello-name.rs +++ b/examples/hello-name.rs @@ -15,7 +15,7 @@ async fn hello_name( ) -> ResultPage { let name = path.into_inner(); Page::new(request) - .with_body(PrepareMarkup::With(html! { h1 { "Hello " (name) "!" } })) + .with_component(Html::with(html! { h1 { "Hello " (name) "!" } })) .render() } diff --git a/pagetop/examples/hello-world.rs b/examples/hello-world.rs similarity index 86% rename from pagetop/examples/hello-world.rs rename to examples/hello-world.rs index c904eb07..17c1e9d3 100644 --- a/pagetop/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -10,7 +10,7 @@ impl PackageTrait for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage { Page::new(request) - .with_body(PrepareMarkup::With(html! { h1 { "Hello World!" } })) + .with_component(Html::with(html! { h1 { "Hello World!" } })) .render() } diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md new file mode 100644 index 00000000..0e80b363 --- /dev/null +++ b/helpers/pagetop-build/README.md @@ -0,0 +1,37 @@ +
+ +

PageTop Build

+ +

Simplifies the process of embedding resources in PageTop app binaries.

+ +[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?style=for-the-badge)](#-license) +[![API Docs](https://img.shields.io/docsrs/pagetop-build?label=API%20Docs&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) +[![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) +[![Downloads](https://img.shields.io/crates/d/pagetop-build.svg?style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) + +
+ +# 📦 About PageTop + +[PageTop](https://docs.rs/pagetop) is an opinionated web framework to build modular *Server-Side +Rendering* web solutions. + + +# 🚧 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**. + + +# 📜 License + +All code in this crate is dual-licensed under either: + + * MIT License + ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) + + * Apache License, Version 2.0, + ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) + +at your option. This means you can select the license you prefer! This dual-licensing approach is +the de-facto standard in the Rust ecosystem. diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index 1b2fb39a..0e36f2dc 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -1,4 +1,5 @@ -//! Provide an easy way to embed static files or compiled SCSS files into your binary at compile +//! **`StaticFilesBundle`** uses [static_files](https://docs.rs/static-files/latest/static_files/) +//! to provide an easy way to embed static files or compiled SCSS files into your binary at compile //! time. //! //! ## Adding to your project @@ -23,9 +24,9 @@ //! use pagetop_build::StaticFilesBundle; //! //! fn main() -> std::io::Result<()> { -//! StaticFilesBundle::from_dir("./static", None) -//! .with_name("guides") -//! .build() +//! StaticFilesBundle::from_dir("./static", None) // Include all files. +//! .with_name("guides") // Name the generated module. +//! .build() // Build the bundle. //! } //! ``` //! @@ -66,7 +67,7 @@ //! //! ## Generated module //! -//! [`StaticFilesBundle`] generates a file in the standard directory +//! `StaticFilesBundle` generates a file in the standard directory //! [OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html) where all //! intermediate and output artifacts are placed during compilation. For example, if you use //! `with_name("guides")`, it generates a file named `guides.rs`: @@ -77,15 +78,15 @@ //! ```rust#ignore //! use pagetop::prelude::*; //! -//! include_files!(guides); +//! static_files!(guides); //! ``` //! -//! Or, access the entire bundle as a global static `HashMap`: +//! Or, access the entire bundle as a static `HashMap`: //! //! ```rust#ignore //! use pagetop::prelude::*; //! -//! include_files!(guides => BUNDLE_GUIDES); +//! static_files!(guides => BUNDLE_GUIDES); //! ``` //! //! You can build more than one resources file to compile with your project. @@ -97,8 +98,6 @@ use std::fs::{create_dir_all, remove_dir_all, File}; use std::io::Write; use std::path::Path; -/// Generates the resources to embed at compile time using -/// [static_files](https://docs.rs/static-files/latest/static_files/). pub struct StaticFilesBundle { resource_dir: ResourceDir, } diff --git a/packages/pagetop-seaorm/README.md b/helpers/pagetop-macros/README.md similarity index 53% rename from packages/pagetop-seaorm/README.md rename to helpers/pagetop-macros/README.md index 189012e8..6dd80331 100644 --- a/packages/pagetop-seaorm/README.md +++ b/helpers/pagetop-macros/README.md @@ -1,22 +1,16 @@
-

PageTop SeaORM

+

PageTop Macros

-

Integrate SeaORM as the database framework for PageTop applications.

+

A collection of macros that boost PageTop development.

[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?style=for-the-badge)](#-license) -[![API Docs](https://img.shields.io/docsrs/pagetop-seaorm?label=API%20Docs&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-seaorm) -[![Crates.io](https://img.shields.io/crates/v/pagetop-seaorm.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-seaorm) -[![Downloads](https://img.shields.io/crates/d/pagetop-seaorm.svg?style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-seaorm) +[![API Docs](https://img.shields.io/docsrs/pagetop-macros?label=API%20Docs&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) +[![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) +[![Downloads](https://img.shields.io/crates/d/pagetop-macros.svg?style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros)
-PageTop SeaORM employs [SQLx](https://crates.io/crates/sqlx) and -[SeaQuery](https://crates.io/crates/sea-query), complemented by a custom version of -[SeaORM Migration](https://github.com/SeaQL/sea-orm/tree/1.1.1/sea-orm-migration/src) (v1.1.1). The -modified SeaORM Migration ensures migrations are scoped per package, providing greater control and -reducing coupling between database components. - # 📦 About PageTop [PageTop](https://docs.rs/pagetop) is an opinionated web framework to build modular *Server-Side @@ -29,6 +23,20 @@ Rendering* web solutions. frequent changes. Production use is not recommended until version **0.1.0**. +# 🔖 Credits + +This crate includes an adapted version of [maud-macros](https://crates.io/crates/maud_macros), +version [0.25.0](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud_macros), by +[Chris Wong](https://crates.io/users/lambda-fairy). + +It also includes an adapted version of [SmartDefault](https://crates.io/crates/smart_default) +(version 0.7.1) by [Jane Doe](https://crates.io/users/jane-doe), renamed as `AutoDefault`, to +streamline the implementation of `Default` in **PageTop** projects. + +Both adaptations eliminate the need to explicitly add `maud` or `smart_default` as dependencies in +`Cargo.toml` files. + + # 📜 License All code in this crate is dual-licensed under either: diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 947327e7..006b92d4 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -3,110 +3,8 @@ mod smart_default; use proc_macro::TokenStream; use proc_macro_error::proc_macro_error; -use quote::{quote, quote_spanned, ToTokens}; -use syn::{parse_macro_input, parse_str, DeriveInput, ItemFn}; - -/// Macro attribute to generate builder methods from `set_` methods. -/// -/// This macro takes a method with the `set_` prefix and generates a corresponding method with the -/// `with_` prefix to use in the builder pattern. -/// -/// # Panics -/// -/// This function will panic if a parameter identifier is not found in the argument list. -/// -/// # Examples -/// -/// ``` -/// #[fn_builder] -/// pub fn set_example(&mut self) -> &mut Self { -/// // implementation -/// } -/// ``` -/// -/// Will generate: -/// -/// ``` -/// pub fn with_example(mut self) -> Self { -/// self.set_example(); -/// self -/// } -/// ``` -#[proc_macro_attribute] -pub fn fn_builder(_: TokenStream, item: TokenStream) -> TokenStream { - let fn_set = parse_macro_input!(item as ItemFn); - let fn_set_name = fn_set.sig.ident.to_string(); - - if !fn_set_name.starts_with("set_") { - let expanded = quote_spanned! { - fn_set.sig.ident.span() => - compile_error!("expected a \"pub fn set_...() -> &mut Self\" method"); - }; - return expanded.into(); - } - - let fn_with_name = fn_set_name.replace("set_", "with_"); - let fn_with_generics = if fn_set.sig.generics.params.is_empty() { - fn_with_name.clone() - } else { - let g = &fn_set.sig.generics; - format!("{fn_with_name}{}", quote! { #g }.to_string()) - }; - - let where_clause = fn_set - .sig - .generics - .where_clause - .as_ref() - .map_or(String::new(), |where_clause| { - format!("{} ", quote! { #where_clause }.to_string()) - }); - - let args: Vec = fn_set - .sig - .inputs - .iter() - .skip(1) - .map(|arg| arg.to_token_stream().to_string()) - .collect(); - - let params: Vec = args - .iter() - .map(|arg| { - arg.split_whitespace() - .next() - .unwrap() - .trim_end_matches(':') - .to_string() - }) - .collect(); - - #[rustfmt::skip] - let fn_with = parse_str::(format!(r#" - pub fn {fn_with_generics}(mut self, {}) -> Self {where_clause} {{ - self.{fn_set_name}({}); - self - }} - "#, args.join(", "), params.join(", ") - ).as_str()).unwrap(); - - #[rustfmt::skip] - let fn_set_doc = format!(r##" -

Use - pub fn {fn_with_name}(self, …) -> Self - for the builder pattern. -

- "##); - - let expanded = quote! { - #[doc(hidden)] - #fn_with - #[inline] - #[doc = #fn_set_doc] - #fn_set - }; - expanded.into() -} +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; #[proc_macro] #[proc_macro_error] diff --git a/drust/Cargo.toml b/packages/drust/Cargo.toml similarity index 100% rename from drust/Cargo.toml rename to packages/drust/Cargo.toml diff --git a/packages/drust/config/common.toml b/packages/drust/config/common.toml new file mode 100644 index 00000000..2b018a16 --- /dev/null +++ b/packages/drust/config/common.toml @@ -0,0 +1,6 @@ +[app] +name = "Drust" +description = "A modern web Content Management System to share your world." + +[database] +db_type = "mysql" diff --git a/config/default.toml b/packages/drust/config/default.toml similarity index 100% rename from config/default.toml rename to packages/drust/config/default.toml diff --git a/config/local.default.toml b/packages/drust/config/local.default.toml similarity index 100% rename from config/local.default.toml rename to packages/drust/config/local.default.toml diff --git a/drust/src/main.rs b/packages/drust/src/main.rs similarity index 100% rename from drust/src/main.rs rename to packages/drust/src/main.rs diff --git a/packages/pagetop-aliner/Cargo.toml b/packages/pagetop-aliner/Cargo.toml index dc57f59f..c25be723 100644 --- a/packages/pagetop-aliner/Cargo.toml +++ b/packages/pagetop-aliner/Cargo.toml @@ -19,6 +19,7 @@ pagetop.workspace = true include_dir.workspace = true static-files.workspace = true + tera = "1.20.0" [build-dependencies] diff --git a/packages/pagetop-aliner/src/lib.rs b/packages/pagetop-aliner/src/lib.rs index 166a9c23..414e1a5a 100644 --- a/packages/pagetop-aliner/src/lib.rs +++ b/packages/pagetop-aliner/src/lib.rs @@ -5,9 +5,9 @@ use tera::Tera; use std::sync::LazyLock; -include_locales!(LOCALES_ALINER); +static_locales!(LOCALES_ALINER); -include_files!(aliner); +static_files!(aliner); // ALINER THEME ************************************************************************************ @@ -50,7 +50,7 @@ impl PackageTrait for Aliner { } fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { - include_files_service!(scfg, aliner => "/aliner"); + static_files_service!(scfg, aliner => "/aliner"); } } diff --git a/packages/pagetop-bootsier/src/lib.rs b/packages/pagetop-bootsier/src/lib.rs index bdab71d4..7c78c2b1 100644 --- a/packages/pagetop-bootsier/src/lib.rs +++ b/packages/pagetop-bootsier/src/lib.rs @@ -1,8 +1,8 @@ use pagetop::prelude::*; -include_locales!(LOCALES_BOOTSIER); +static_locales!(LOCALES_BOOTSIER); -//include_files!(bootsier); +//static_files!(bootsier); pub struct Bootsier; @@ -26,7 +26,7 @@ impl PackageTrait for Bootsier { } fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { - include_files_service!(scfg, bootsier => "/bootsier"); + static_files_service!(scfg, bootsier => "/bootsier"); } */ } diff --git a/packages/pagetop-seaorm/Cargo.toml b/packages/pagetop-seaorm/Cargo.toml deleted file mode 100644 index 9ee0a2f3..00000000 --- a/packages/pagetop-seaorm/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "pagetop-seaorm" -version = "0.0.1" -edition = "2021" - -description = """\ - Integrate SeaORM as the database framework for PageTop applications.\ -""" -categories = ["web-programming", "database"] -keywords = ["pagetop", "database", "sql", "orm"] - -homepage = { workspace = true } -repository = { workspace = true } -authors = { workspace = true } -license = { workspace = true } - -[dependencies] -pagetop.workspace = true - -async-trait = "0.1.83" -futures = "0.3.31" -serde.workspace = true -static-files.workspace = true -url = "2.5.4" - -[dependencies.sea-orm] -version = "1.1.1" -features = [ - "debug-print", "macros", "runtime-async-std-native-tls", - "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", -] -default-features = false - -[dependencies.sea-schema] -version = "0.16.0" diff --git a/packages/pagetop-seaorm/src/config.rs b/packages/pagetop-seaorm/src/config.rs deleted file mode 100644 index f423cb7e..00000000 --- a/packages/pagetop-seaorm/src/config.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! Configuration settings for the SeaORM PageTop package. -//! -//! Example: -//! -//! ```toml -//! [database] -//! db_type = "mysql" -//! db_name = "db" -//! db_user = "user" -//! db_pass = "password" -//! db_host = "localhost" -//! db_port = 3306 -//! max_pool_size = 5 -//! ``` -//! -//! Usage: -//! -//! ```rust -//! use pagetop_seaorm::config; -//! -//! assert_eq!(config::SETTINGS.database.db_host, "localhost"); -//! ``` -//! See [`pagetop::include_config`] to learn how **PageTop** read configuration files and use -//! settings. - -use pagetop::prelude::*; - -use serde::Deserialize; - -include_config!(SETTINGS: Settings => [ - // [database] - "database.db_type" => "", - "database.db_name" => "", - "database.db_user" => "", - "database.db_pass" => "", - "database.db_host" => "localhost", - "database.db_port" => 0, - "database.max_pool_size" => 5, -]); - -#[derive(Debug, Deserialize)] -/// Represents configuration settings, specifically the [`[database]`](Database) section (used by -/// [`SETTINGS`]). -pub struct Settings { - pub database: Database, -} -#[derive(Debug, Deserialize)] -/// Represents the `[database]` section in the [`Settings`] type. -pub struct Database { - /// Type of database: *"mysql"*, *"postgres"*, or *"sqlite"*. - /// Default: *""*. - pub db_type: String, - /// Name (for MySQL/Postgres) or reference (for SQLite) of the database. - /// Default: *""*. - pub db_name: String, - /// Username for database connection (for MySQL/Postgres). - /// Default: *""*. - pub db_user: String, - /// Password for database connection (for MySQL/Postgres). - /// Default: *""*. - pub db_pass: String, - /// Hostname for database connection (for MySQL/Postgres). - /// Default: *"localhost"*. - pub db_host: String, - /// Port number for database connection, typically 3306 (MySQL) or 5432 (Postgres). - /// Default: *0*. - pub db_port: u16, - /// Maximum number of allowed connections. - /// Default: *5*. - pub max_pool_size: u32, -} diff --git a/packages/pagetop-seaorm/src/db.rs b/packages/pagetop-seaorm/src/db.rs deleted file mode 100644 index cece9a8f..00000000 --- a/packages/pagetop-seaorm/src/db.rs +++ /dev/null @@ -1,132 +0,0 @@ -use pagetop::trace; -use pagetop::util::TypeInfo; - -pub use url::Url as DbUri; - -pub use sea_orm::error::{DbErr, RuntimeErr}; -pub use sea_orm::{DatabaseConnection as DbConn, ExecResult, QueryResult}; - -use sea_orm::{ConnectionTrait, DatabaseBackend, Statement}; - -mod dbconn; -pub(crate) use dbconn::{run_now, DBCONN}; - -// The migration module is a customized version of the sea_orm_migration module (v1.0.0) -// https://github.com/SeaQL/sea-orm/tree/1.0.0/sea-orm-migration to avoid errors caused by the -// package paradigm of PageTop. Files integrated from original: -// -// lib.rs => db/migration.rs . . . . . . . . . . . . . . (excluding some modules and exports) -// connection.rs => db/migration/connection.rs . . . . . . . . . . . . . . (full integration) -// manager.rs => db/migration/manager.rs . . . . . . . . . . . . . . . . . (full integration) -// migrator.rs => db/migration/migrator.rs . . . . . . . . . . . .(omitting error management) -// prelude.rs => db/migration/prelude.rs . . . . . . . . . . . . . . . . . . . (avoiding CLI) -// seaql_migrations.rs => db/migration/seaql_migrations.rs . . . . . . . . (full integration) -// -mod migration; -pub use migration::prelude::*; -pub use migration::schema::*; - -pub async fn query(stmt: &mut Q) -> Result, DbErr> { - let dbconn = &*DBCONN; - let dbbackend = dbconn.get_database_backend(); - dbconn - .query_all(Statement::from_string( - dbbackend, - match dbbackend { - DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), - DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), - DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), - }, - )) - .await -} - -pub async fn exec(stmt: &mut Q) -> Result, DbErr> { - let dbconn = &*DBCONN; - let dbbackend = dbconn.get_database_backend(); - dbconn - .query_one(Statement::from_string( - dbbackend, - match dbbackend { - DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), - DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), - DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), - }, - )) - .await -} - -pub async fn exec_raw(stmt: String) -> Result { - let dbconn = &*DBCONN; - let dbbackend = dbconn.get_database_backend(); - dbconn - .execute(Statement::from_string(dbbackend, stmt)) - .await -} - -pub trait MigratorBase { - fn run_up(); - - fn run_down(); -} - -#[rustfmt::skip] -impl MigratorBase for M { - fn run_up() { - if let Err(e) = run_now(Self::up(SchemaManagerConnection::Connection(&DBCONN), None)) { - trace::error!("Migration upgrade failed ({})", e); - }; - } - - fn run_down() { - if let Err(e) = run_now(Self::down(SchemaManagerConnection::Connection(&DBCONN), None)) { - trace::error!("Migration downgrade failed ({})", e); - }; - } -} - -impl MigrationName for M { - fn name(&self) -> &str { - TypeInfo::NameTo(-2).of::() - } -} - -pub type MigrationItem = Box; - -#[macro_export] -macro_rules! install_migrations { - ( $($migration_module:ident),+ $(,)? ) => {{ - use $crate::db::{MigrationItem, MigratorBase, MigratorTrait}; - - struct Migrator; - impl MigratorTrait for Migrator { - fn migrations() -> Vec { - let mut m = Vec::::new(); - $( - m.push(Box::new(migration::$migration_module::Migration)); - )* - m - } - } - Migrator::run_up(); - }}; -} - -#[macro_export] -macro_rules! uninstall_migrations { - ( $($migration_module:ident),+ $(,)? ) => {{ - use $crate::db::{MigrationItem, MigratorBase, MigratorTrait}; - - struct Migrator; - impl MigratorTrait for Migrator { - fn migrations() -> Vec { - let mut m = Vec::::new(); - $( - m.push(Box::new(migration::$migration_module::Migration)); - )* - m - } - } - Migrator::run_down(); - }}; -} diff --git a/packages/pagetop-seaorm/src/db/dbconn.rs b/packages/pagetop-seaorm/src/db/dbconn.rs deleted file mode 100644 index bd227956..00000000 --- a/packages/pagetop-seaorm/src/db/dbconn.rs +++ /dev/null @@ -1,69 +0,0 @@ -use pagetop::trace; - -use crate::config; -use crate::db::{DbConn, DbUri}; - -use std::sync::LazyLock; - -use sea_orm::{ConnectOptions, Database}; - -pub use futures::executor::block_on as run_now; - -pub static DBCONN: LazyLock = LazyLock::new(|| { - trace::info!( - "Connecting to database \"{}\" using a pool of {} connections", - &config::SETTINGS.database.db_name, - &config::SETTINGS.database.max_pool_size - ); - - let db_uri = match config::SETTINGS.database.db_type.as_str() { - "mysql" | "postgres" => { - let mut tmp_uri = DbUri::parse( - format!( - "{}://{}/{}", - &config::SETTINGS.database.db_type, - &config::SETTINGS.database.db_host, - &config::SETTINGS.database.db_name - ) - .as_str(), - ) - .unwrap(); - tmp_uri - .set_username(config::SETTINGS.database.db_user.as_str()) - .unwrap(); - // https://github.com/launchbadge/sqlx/issues/1624 - tmp_uri - .set_password(Some(config::SETTINGS.database.db_pass.as_str())) - .unwrap(); - if config::SETTINGS.database.db_port != 0 { - tmp_uri - .set_port(Some(config::SETTINGS.database.db_port)) - .unwrap(); - } - tmp_uri - } - "sqlite" => DbUri::parse( - format!( - "{}://{}", - &config::SETTINGS.database.db_type, - &config::SETTINGS.database.db_name - ) - .as_str(), - ) - .unwrap(), - _ => { - trace::error!( - "Unrecognized database type \"{}\"", - &config::SETTINGS.database.db_type - ); - DbUri::parse("").unwrap() - } - }; - - run_now(Database::connect::({ - let mut db_opt = ConnectOptions::new(db_uri.to_string()); - db_opt.max_connections(config::SETTINGS.database.max_pool_size); - db_opt - })) - .unwrap_or_else(|_| panic!("Failed to connect to database")) -}); diff --git a/packages/pagetop-seaorm/src/db/migration.rs b/packages/pagetop-seaorm/src/db/migration.rs deleted file mode 100644 index 29314bf6..00000000 --- a/packages/pagetop-seaorm/src/db/migration.rs +++ /dev/null @@ -1,33 +0,0 @@ -//pub mod cli; -pub mod connection; -pub mod manager; -pub mod migrator; -pub mod prelude; -pub mod schema; -pub mod seaql_migrations; -//pub mod util; - -pub use connection::*; -pub use manager::*; -//pub use migrator::*; - -pub use async_trait; -//pub use sea_orm; -//pub use sea_orm::sea_query; -use sea_orm::DbErr; - -pub trait MigrationName { - fn name(&self) -> &str; -} - -/// The migration definition -#[async_trait::async_trait] -pub trait MigrationTrait: MigrationName + Send + Sync { - /// Define actions to perform when applying the migration - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>; - - /// Define actions to perform when rolling back the migration - async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { - Err(DbErr::Migration("We Don't Do That Here".to_owned())) - } -} diff --git a/packages/pagetop-seaorm/src/db/migration/connection.rs b/packages/pagetop-seaorm/src/db/migration/connection.rs deleted file mode 100644 index 116185e4..00000000 --- a/packages/pagetop-seaorm/src/db/migration/connection.rs +++ /dev/null @@ -1,148 +0,0 @@ -use futures::Future; -use sea_orm::{ - AccessMode, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbBackend, DbErr, - ExecResult, IsolationLevel, QueryResult, Statement, TransactionError, TransactionTrait, -}; -use std::pin::Pin; - -pub enum SchemaManagerConnection<'c> { - Connection(&'c DatabaseConnection), - Transaction(&'c DatabaseTransaction), -} - -#[async_trait::async_trait] -impl<'c> ConnectionTrait for SchemaManagerConnection<'c> { - fn get_database_backend(&self) -> DbBackend { - match self { - SchemaManagerConnection::Connection(conn) => conn.get_database_backend(), - SchemaManagerConnection::Transaction(trans) => trans.get_database_backend(), - } - } - - async fn execute(&self, stmt: Statement) -> Result { - match self { - SchemaManagerConnection::Connection(conn) => conn.execute(stmt).await, - SchemaManagerConnection::Transaction(trans) => trans.execute(stmt).await, - } - } - - async fn execute_unprepared(&self, sql: &str) -> Result { - match self { - SchemaManagerConnection::Connection(conn) => conn.execute_unprepared(sql).await, - SchemaManagerConnection::Transaction(trans) => trans.execute_unprepared(sql).await, - } - } - - async fn query_one(&self, stmt: Statement) -> Result, DbErr> { - match self { - SchemaManagerConnection::Connection(conn) => conn.query_one(stmt).await, - SchemaManagerConnection::Transaction(trans) => trans.query_one(stmt).await, - } - } - - async fn query_all(&self, stmt: Statement) -> Result, DbErr> { - match self { - SchemaManagerConnection::Connection(conn) => conn.query_all(stmt).await, - SchemaManagerConnection::Transaction(trans) => trans.query_all(stmt).await, - } - } - - fn is_mock_connection(&self) -> bool { - match self { - SchemaManagerConnection::Connection(conn) => conn.is_mock_connection(), - SchemaManagerConnection::Transaction(trans) => trans.is_mock_connection(), - } - } -} - -#[async_trait::async_trait] -impl<'c> TransactionTrait for SchemaManagerConnection<'c> { - async fn begin(&self) -> Result { - match self { - SchemaManagerConnection::Connection(conn) => conn.begin().await, - SchemaManagerConnection::Transaction(trans) => trans.begin().await, - } - } - - async fn begin_with_config( - &self, - isolation_level: Option, - access_mode: Option, - ) -> Result { - match self { - SchemaManagerConnection::Connection(conn) => { - conn.begin_with_config(isolation_level, access_mode).await - } - SchemaManagerConnection::Transaction(trans) => { - trans.begin_with_config(isolation_level, access_mode).await - } - } - } - - async fn transaction(&self, callback: F) -> Result> - where - F: for<'a> FnOnce( - &'a DatabaseTransaction, - ) -> Pin> + Send + 'a>> - + Send, - T: Send, - E: std::error::Error + Send, - { - match self { - SchemaManagerConnection::Connection(conn) => conn.transaction(callback).await, - SchemaManagerConnection::Transaction(trans) => trans.transaction(callback).await, - } - } - - async fn transaction_with_config( - &self, - callback: F, - isolation_level: Option, - access_mode: Option, - ) -> Result> - where - F: for<'a> FnOnce( - &'a DatabaseTransaction, - ) -> Pin> + Send + 'a>> - + Send, - T: Send, - E: std::error::Error + Send, - { - match self { - SchemaManagerConnection::Connection(conn) => { - conn.transaction_with_config(callback, isolation_level, access_mode) - .await - } - SchemaManagerConnection::Transaction(trans) => { - trans - .transaction_with_config(callback, isolation_level, access_mode) - .await - } - } - } -} - -pub trait IntoSchemaManagerConnection<'c>: Send -where - Self: 'c, -{ - fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c>; -} - -impl<'c> IntoSchemaManagerConnection<'c> for SchemaManagerConnection<'c> { - fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c> { - self - } -} - -impl<'c> IntoSchemaManagerConnection<'c> for &'c DatabaseConnection { - fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c> { - SchemaManagerConnection::Connection(self) - } -} - -impl<'c> IntoSchemaManagerConnection<'c> for &'c DatabaseTransaction { - fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c> { - SchemaManagerConnection::Transaction(self) - } -} diff --git a/packages/pagetop-seaorm/src/db/migration/manager.rs b/packages/pagetop-seaorm/src/db/migration/manager.rs deleted file mode 100644 index d1cc3b6a..00000000 --- a/packages/pagetop-seaorm/src/db/migration/manager.rs +++ /dev/null @@ -1,167 +0,0 @@ -use super::{IntoSchemaManagerConnection, SchemaManagerConnection}; -use sea_orm::sea_query::{ - extension::postgres::{TypeAlterStatement, TypeCreateStatement, TypeDropStatement}, - ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement, IndexDropStatement, - TableAlterStatement, TableCreateStatement, TableDropStatement, TableRenameStatement, - TableTruncateStatement, -}; -use sea_orm::{ConnectionTrait, DbBackend, DbErr, StatementBuilder}; -use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite}; - -/// Helper struct for writing migration scripts in migration file -pub struct SchemaManager<'c> { - conn: SchemaManagerConnection<'c>, -} - -impl<'c> SchemaManager<'c> { - pub fn new(conn: T) -> Self - where - T: IntoSchemaManagerConnection<'c>, - { - Self { - conn: conn.into_schema_manager_connection(), - } - } - - pub async fn exec_stmt(&self, stmt: S) -> Result<(), DbErr> - where - S: StatementBuilder, - { - let builder = self.conn.get_database_backend(); - self.conn.execute(builder.build(&stmt)).await.map(|_| ()) - } - - pub fn get_database_backend(&self) -> DbBackend { - self.conn.get_database_backend() - } - - pub fn get_connection(&self) -> &SchemaManagerConnection<'c> { - &self.conn - } -} - -/// Schema Creation -impl<'c> SchemaManager<'c> { - pub async fn create_table(&self, stmt: TableCreateStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } - - pub async fn create_index(&self, stmt: IndexCreateStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } - - pub async fn create_foreign_key(&self, stmt: ForeignKeyCreateStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } - - pub async fn create_type(&self, stmt: TypeCreateStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } -} - -/// Schema Mutation -impl<'c> SchemaManager<'c> { - pub async fn alter_table(&self, stmt: TableAlterStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } - - pub async fn drop_table(&self, stmt: TableDropStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } - - pub async fn rename_table(&self, stmt: TableRenameStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } - - pub async fn truncate_table(&self, stmt: TableTruncateStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } - - pub async fn drop_index(&self, stmt: IndexDropStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } - - pub async fn drop_foreign_key(&self, stmt: ForeignKeyDropStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } - - pub async fn alter_type(&self, stmt: TypeAlterStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } - - pub async fn drop_type(&self, stmt: TypeDropStatement) -> Result<(), DbErr> { - self.exec_stmt(stmt).await - } -} - -/// Schema Inspection. -impl<'c> SchemaManager<'c> { - pub async fn has_table(&self, table: T) -> Result - where - T: AsRef, - { - has_table(&self.conn, table).await - } - - pub async fn has_column(&self, table: T, column: C) -> Result - where - T: AsRef, - C: AsRef, - { - let stmt = match self.conn.get_database_backend() { - DbBackend::MySql => MySql.has_column(table, column), - DbBackend::Postgres => Postgres.has_column(table, column), - DbBackend::Sqlite => Sqlite.has_column(table, column), - }; - - let builder = self.conn.get_database_backend(); - let res = self - .conn - .query_one(builder.build(&stmt)) - .await? - .ok_or_else(|| DbErr::Custom("Failed to check column exists".to_owned()))?; - - res.try_get("", "has_column") - } - - pub async fn has_index(&self, table: T, index: I) -> Result - where - T: AsRef, - I: AsRef, - { - let stmt = match self.conn.get_database_backend() { - DbBackend::MySql => MySql.has_index(table, index), - DbBackend::Postgres => Postgres.has_index(table, index), - DbBackend::Sqlite => Sqlite.has_index(table, index), - }; - - let builder = self.conn.get_database_backend(); - let res = self - .conn - .query_one(builder.build(&stmt)) - .await? - .ok_or_else(|| DbErr::Custom("Failed to check index exists".to_owned()))?; - - res.try_get("", "has_index") - } -} - -pub(crate) async fn has_table(conn: &C, table: T) -> Result -where - C: ConnectionTrait, - T: AsRef, -{ - let stmt = match conn.get_database_backend() { - DbBackend::MySql => MySql.has_table(table), - DbBackend::Postgres => Postgres.has_table(table), - DbBackend::Sqlite => Sqlite.has_table(table), - }; - - let builder = conn.get_database_backend(); - let res = conn - .query_one(builder.build(&stmt)) - .await? - .ok_or_else(|| DbErr::Custom("Failed to check table exists".to_owned()))?; - - res.try_get("", "has_table") -} diff --git a/packages/pagetop-seaorm/src/db/migration/migrator.rs b/packages/pagetop-seaorm/src/db/migration/migrator.rs deleted file mode 100644 index 06611412..00000000 --- a/packages/pagetop-seaorm/src/db/migration/migrator.rs +++ /dev/null @@ -1,593 +0,0 @@ -use futures::Future; -use std::collections::HashSet; -use std::fmt::Display; -use std::pin::Pin; -use std::time::SystemTime; - -use pagetop::trace::info; - -use sea_orm::sea_query::{ - self, extension::postgres::Type, Alias, Expr, ForeignKey, IntoIden, JoinType, Order, Query, - SelectStatement, SimpleExpr, Table, -}; -use sea_orm::{ - ActiveModelTrait, ActiveValue, Condition, ConnectionTrait, DbBackend, DbErr, DeriveIden, - DynIden, EntityTrait, FromQueryResult, Iterable, QueryFilter, Schema, Statement, - TransactionTrait, -}; -use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite}; - -use super::{seaql_migrations, IntoSchemaManagerConnection, MigrationTrait, SchemaManager}; - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -/// Status of migration -pub enum MigrationStatus { - /// Not yet applied - Pending, - /// Applied - Applied, -} - -impl Display for MigrationStatus { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let status = match self { - MigrationStatus::Pending => "Pending", - MigrationStatus::Applied => "Applied", - }; - write!(f, "{status}") - } -} - -pub struct Migration { - migration: Box, - status: MigrationStatus, -} - -impl Migration { - /// Get migration name from MigrationName trait implementation - pub fn name(&self) -> &str { - self.migration.name() - } - - /// Get migration status - pub fn status(&self) -> MigrationStatus { - self.status - } -} - -/// Performing migrations on a database -#[async_trait::async_trait] -pub trait MigratorTrait: Send { - /// Vector of migrations in time sequence - fn migrations() -> Vec>; - - /// Name of the migration table, it is `seaql_migrations` by default - fn migration_table_name() -> DynIden { - seaql_migrations::Entity.into_iden() - } - - /// Get list of migrations wrapped in `Migration` struct - fn get_migration_files() -> Vec { - Self::migrations() - .into_iter() - .map(|migration| Migration { - migration, - status: MigrationStatus::Pending, - }) - .collect() - } - - /// Get list of applied migrations from database - async fn get_migration_models(db: &C) -> Result, DbErr> - where - C: ConnectionTrait, - { - Self::install(db).await?; - let stmt = Query::select() - .table_name(Self::migration_table_name()) - .columns(seaql_migrations::Column::iter().map(IntoIden::into_iden)) - .order_by(seaql_migrations::Column::Version, Order::Asc) - .to_owned(); - let builder = db.get_database_backend(); - seaql_migrations::Model::find_by_statement(builder.build(&stmt)) - .all(db) - .await - } - - /// Get list of migrations with status - async fn get_migration_with_status(db: &C) -> Result, DbErr> - where - C: ConnectionTrait, - { - Self::install(db).await?; - let mut migration_files = Self::get_migration_files(); - let migration_models = Self::get_migration_models(db).await?; - - let migration_in_db: HashSet = migration_models - .into_iter() - .map(|model| model.version) - .collect(); - let migration_in_fs: HashSet = migration_files - .iter() - .map(|file| file.migration.name().to_string()) - .collect(); - - let pending_migrations = &migration_in_fs - &migration_in_db; - for migration_file in migration_files.iter_mut() { - if !pending_migrations.contains(migration_file.migration.name()) { - migration_file.status = MigrationStatus::Applied; - } - } - /* - let missing_migrations_in_fs = &migration_in_db - &migration_in_fs; - let errors: Vec = missing_migrations_in_fs - .iter() - .map(|missing_migration| { - format!("Migration file of version '{missing_migration}' is missing, this migration has been applied but its file is missing") - }).collect(); - - if !errors.is_empty() { - Err(DbErr::Custom(errors.join("\n"))) - } else { */ - Ok(migration_files) - /* } */ - } - - /// Get list of pending migrations - async fn get_pending_migrations(db: &C) -> Result, DbErr> - where - C: ConnectionTrait, - { - Self::install(db).await?; - Ok(Self::get_migration_with_status(db) - .await? - .into_iter() - .filter(|file| file.status == MigrationStatus::Pending) - .collect()) - } - - /// Get list of applied migrations - async fn get_applied_migrations(db: &C) -> Result, DbErr> - where - C: ConnectionTrait, - { - Self::install(db).await?; - Ok(Self::get_migration_with_status(db) - .await? - .into_iter() - .filter(|file| file.status == MigrationStatus::Applied) - .collect()) - } - - /// Create migration table `seaql_migrations` in the database - async fn install(db: &C) -> Result<(), DbErr> - where - C: ConnectionTrait, - { - let builder = db.get_database_backend(); - let table_name = Self::migration_table_name(); - let schema = Schema::new(builder); - let mut stmt = schema - .create_table_from_entity(seaql_migrations::Entity) - .table_name(table_name); - stmt.if_not_exists(); - db.execute(builder.build(&stmt)).await.map(|_| ()) - } - - /// Check the status of all migrations - async fn status(db: &C) -> Result<(), DbErr> - where - C: ConnectionTrait, - { - Self::install(db).await?; - - info!("Checking migration status"); - - for Migration { migration, status } in Self::get_migration_with_status(db).await? { - info!("Migration '{}'... {}", migration.name(), status); - } - - Ok(()) - } - - /// Drop all tables from the database, then reapply all migrations - async fn fresh<'c, C>(db: C) -> Result<(), DbErr> - where - C: IntoSchemaManagerConnection<'c>, - { - exec_with_connection::<'_, _, _>(db, move |manager| { - Box::pin(async move { exec_fresh::(manager).await }) - }) - .await - } - - /// Rollback all applied migrations, then reapply all migrations - async fn refresh<'c, C>(db: C) -> Result<(), DbErr> - where - C: IntoSchemaManagerConnection<'c>, - { - exec_with_connection::<'_, _, _>(db, move |manager| { - Box::pin(async move { - exec_down::(manager, None).await?; - exec_up::(manager, None).await - }) - }) - .await - } - - /// Rollback all applied migrations - async fn reset<'c, C>(db: C) -> Result<(), DbErr> - where - C: IntoSchemaManagerConnection<'c>, - { - exec_with_connection::<'_, _, _>(db, move |manager| { - Box::pin(async move { exec_down::(manager, None).await }) - }) - .await - } - - /// Apply pending migrations - async fn up<'c, C>(db: C, steps: Option) -> Result<(), DbErr> - where - C: IntoSchemaManagerConnection<'c>, - { - exec_with_connection::<'_, _, _>(db, move |manager| { - Box::pin(async move { exec_up::(manager, steps).await }) - }) - .await - } - - /// Rollback applied migrations - async fn down<'c, C>(db: C, steps: Option) -> Result<(), DbErr> - where - C: IntoSchemaManagerConnection<'c>, - { - exec_with_connection::<'_, _, _>(db, move |manager| { - Box::pin(async move { exec_down::(manager, steps).await }) - }) - .await - } -} - -async fn exec_with_connection<'c, C, F>(db: C, f: F) -> Result<(), DbErr> -where - C: IntoSchemaManagerConnection<'c>, - F: for<'b> Fn( - &'b SchemaManager<'_>, - ) -> Pin> + Send + 'b>>, -{ - let db = db.into_schema_manager_connection(); - - match db.get_database_backend() { - DbBackend::Postgres => { - let transaction = db.begin().await?; - let manager = SchemaManager::new(&transaction); - f(&manager).await?; - transaction.commit().await - } - DbBackend::MySql | DbBackend::Sqlite => { - let manager = SchemaManager::new(db); - f(&manager).await - } - } -} - -async fn exec_fresh(manager: &SchemaManager<'_>) -> Result<(), DbErr> -where - M: MigratorTrait + ?Sized, -{ - let db = manager.get_connection(); - - M::install(db).await?; - let db_backend = db.get_database_backend(); - - // Temporarily disable the foreign key check - if db_backend == DbBackend::Sqlite { - info!("Disabling foreign key check"); - db.execute(Statement::from_string( - db_backend, - "PRAGMA foreign_keys = OFF".to_owned(), - )) - .await?; - info!("Foreign key check disabled"); - } - - // Drop all foreign keys - if db_backend == DbBackend::MySql { - info!("Dropping all foreign keys"); - let stmt = query_mysql_foreign_keys(db); - let rows = db.query_all(db_backend.build(&stmt)).await?; - for row in rows.into_iter() { - let constraint_name: String = row.try_get("", "CONSTRAINT_NAME")?; - let table_name: String = row.try_get("", "TABLE_NAME")?; - info!( - "Dropping foreign key '{}' from table '{}'", - constraint_name, table_name - ); - let mut stmt = ForeignKey::drop(); - stmt.table(Alias::new(table_name.as_str())) - .name(constraint_name.as_str()); - db.execute(db_backend.build(&stmt)).await?; - info!("Foreign key '{}' has been dropped", constraint_name); - } - info!("All foreign keys dropped"); - } - - // Drop all tables - let stmt = query_tables(db).await; - let rows = db.query_all(db_backend.build(&stmt)).await?; - for row in rows.into_iter() { - let table_name: String = row.try_get("", "table_name")?; - info!("Dropping table '{}'", table_name); - let mut stmt = Table::drop(); - stmt.table(Alias::new(table_name.as_str())) - .if_exists() - .cascade(); - db.execute(db_backend.build(&stmt)).await?; - info!("Table '{}' has been dropped", table_name); - } - - // Drop all types - if db_backend == DbBackend::Postgres { - info!("Dropping all types"); - let stmt = query_pg_types(db); - let rows = db.query_all(db_backend.build(&stmt)).await?; - for row in rows { - let type_name: String = row.try_get("", "typname")?; - info!("Dropping type '{}'", type_name); - let mut stmt = Type::drop(); - stmt.name(Alias::new(&type_name)); - db.execute(db_backend.build(&stmt)).await?; - info!("Type '{}' has been dropped", type_name); - } - } - - // Restore the foreign key check - if db_backend == DbBackend::Sqlite { - info!("Restoring foreign key check"); - db.execute(Statement::from_string( - db_backend, - "PRAGMA foreign_keys = ON".to_owned(), - )) - .await?; - info!("Foreign key check restored"); - } - - // Reapply all migrations - exec_up::(manager, None).await -} - -async fn exec_up(manager: &SchemaManager<'_>, mut steps: Option) -> Result<(), DbErr> -where - M: MigratorTrait + ?Sized, -{ - let db = manager.get_connection(); - - M::install(db).await?; - /* - if let Some(steps) = steps { - info!("Applying {} pending migrations", steps); - } else { - info!("Applying all pending migrations"); - } - */ - let migrations = M::get_pending_migrations(db).await?.into_iter(); - /* - if migrations.len() == 0 { - info!("No pending migrations"); - } - */ - for Migration { migration, .. } in migrations { - if let Some(steps) = steps.as_mut() { - if steps == &0 { - break; - } - *steps -= 1; - } - info!("Applying migration '{}'", migration.name()); - migration.up(manager).await?; - info!("Migration '{}' has been applied", migration.name()); - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("SystemTime before UNIX EPOCH!"); - seaql_migrations::Entity::insert(seaql_migrations::ActiveModel { - version: ActiveValue::Set(migration.name().to_owned()), - applied_at: ActiveValue::Set(now.as_secs() as i64), - }) - .table_name(M::migration_table_name()) - .exec(db) - .await?; - } - - Ok(()) -} - -async fn exec_down(manager: &SchemaManager<'_>, mut steps: Option) -> Result<(), DbErr> -where - M: MigratorTrait + ?Sized, -{ - let db = manager.get_connection(); - - M::install(db).await?; - - if let Some(steps) = steps { - info!("Rolling back {} applied migrations", steps); - } else { - info!("Rolling back all applied migrations"); - } - - let migrations = M::get_applied_migrations(db).await?.into_iter().rev(); - if migrations.len() == 0 { - info!("No applied migrations"); - } - for Migration { migration, .. } in migrations { - if let Some(steps) = steps.as_mut() { - if steps == &0 { - break; - } - *steps -= 1; - } - info!("Rolling back migration '{}'", migration.name()); - migration.down(manager).await?; - info!("Migration '{}' has been rollbacked", migration.name()); - seaql_migrations::Entity::delete_many() - .filter(Expr::col(seaql_migrations::Column::Version).eq(migration.name())) - .table_name(M::migration_table_name()) - .exec(db) - .await?; - } - - Ok(()) -} - -async fn query_tables(db: &C) -> SelectStatement -where - C: ConnectionTrait, -{ - match db.get_database_backend() { - DbBackend::MySql => MySql.query_tables(), - DbBackend::Postgres => Postgres.query_tables(), - DbBackend::Sqlite => Sqlite.query_tables(), - } -} - -fn get_current_schema(db: &C) -> SimpleExpr -where - C: ConnectionTrait, -{ - match db.get_database_backend() { - DbBackend::MySql => MySql::get_current_schema(), - DbBackend::Postgres => Postgres::get_current_schema(), - DbBackend::Sqlite => unimplemented!(), - } -} - -#[derive(DeriveIden)] -enum InformationSchema { - #[sea_orm(iden = "information_schema")] - Schema, - #[sea_orm(iden = "TABLE_NAME")] - TableName, - #[sea_orm(iden = "CONSTRAINT_NAME")] - ConstraintName, - TableConstraints, - TableSchema, - ConstraintType, -} - -fn query_mysql_foreign_keys(db: &C) -> SelectStatement -where - C: ConnectionTrait, -{ - let mut stmt = Query::select(); - stmt.columns([ - InformationSchema::TableName, - InformationSchema::ConstraintName, - ]) - .from(( - InformationSchema::Schema, - InformationSchema::TableConstraints, - )) - .cond_where( - Condition::all() - .add(Expr::expr(get_current_schema(db)).equals(( - InformationSchema::TableConstraints, - InformationSchema::TableSchema, - ))) - .add( - Expr::col(( - InformationSchema::TableConstraints, - InformationSchema::ConstraintType, - )) - .eq("FOREIGN KEY"), - ), - ); - stmt -} - -#[derive(DeriveIden)] -enum PgType { - Table, - Typname, - Typnamespace, - Typelem, -} - -#[derive(DeriveIden)] -enum PgNamespace { - Table, - Oid, - Nspname, -} - -fn query_pg_types(db: &C) -> SelectStatement -where - C: ConnectionTrait, -{ - let mut stmt = Query::select(); - stmt.column(PgType::Typname) - .from(PgType::Table) - .join( - JoinType::LeftJoin, - PgNamespace::Table, - Expr::col((PgNamespace::Table, PgNamespace::Oid)) - .equals((PgType::Table, PgType::Typnamespace)), - ) - .cond_where( - Condition::all() - .add( - Expr::expr(get_current_schema(db)) - .equals((PgNamespace::Table, PgNamespace::Nspname)), - ) - .add(Expr::col((PgType::Table, PgType::Typelem)).eq(0)), - ); - stmt -} - -trait QueryTable { - type Statement; - - fn table_name(self, table_name: DynIden) -> Self::Statement; -} - -impl QueryTable for SelectStatement { - type Statement = SelectStatement; - - fn table_name(mut self, table_name: DynIden) -> SelectStatement { - self.from(table_name); - self - } -} - -impl QueryTable for sea_query::TableCreateStatement { - type Statement = sea_query::TableCreateStatement; - - fn table_name(mut self, table_name: DynIden) -> sea_query::TableCreateStatement { - self.table(table_name); - self - } -} - -impl QueryTable for sea_orm::Insert -where - A: ActiveModelTrait, -{ - type Statement = sea_orm::Insert; - - fn table_name(mut self, table_name: DynIden) -> sea_orm::Insert { - sea_orm::QueryTrait::query(&mut self).into_table(table_name); - self - } -} - -impl QueryTable for sea_orm::DeleteMany -where - E: EntityTrait, -{ - type Statement = sea_orm::DeleteMany; - - fn table_name(mut self, table_name: DynIden) -> sea_orm::DeleteMany { - sea_orm::QueryTrait::query(&mut self).from_table(table_name); - self - } -} diff --git a/packages/pagetop-seaorm/src/db/migration/prelude.rs b/packages/pagetop-seaorm/src/db/migration/prelude.rs deleted file mode 100644 index 5556a094..00000000 --- a/packages/pagetop-seaorm/src/db/migration/prelude.rs +++ /dev/null @@ -1,13 +0,0 @@ -//pub use super::cli; - -pub use super::connection::IntoSchemaManagerConnection; -pub use super::connection::SchemaManagerConnection; -pub use super::manager::SchemaManager; -pub use super::migrator::MigratorTrait; -pub use super::{MigrationName, MigrationTrait}; -pub use async_trait; -pub use sea_orm; -pub use sea_orm::sea_query; -pub use sea_orm::sea_query::*; -pub use sea_orm::DeriveIden; -pub use sea_orm::DeriveMigrationName; diff --git a/packages/pagetop-seaorm/src/db/migration/schema.rs b/packages/pagetop-seaorm/src/db/migration/schema.rs deleted file mode 100644 index cb013c59..00000000 --- a/packages/pagetop-seaorm/src/db/migration/schema.rs +++ /dev/null @@ -1,613 +0,0 @@ -//! > Adapted from -//! -//! # Database Table Schema Helpers -//! -//! This module defines functions and helpers for creating database table -//! schemas using the `sea-orm` and `sea-query` libraries. -//! -//! # Example -//! -//! The following example shows how the user migration file should be and using -//! the schema helpers to create the Db fields. -//! -//! ```rust -//! use sea_orm_migration::{prelude::*, schema::*}; -//! -//! #[derive(DeriveMigrationName)] -//! pub struct Migration; -//! -//! #[async_trait::async_trait] -//! impl MigrationTrait for Migration { -//! async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { -//! let table = table_auto(Users::Table) -//! .col(pk_auto(Users::Id)) -//! .col(uuid(Users::Pid)) -//! .col(string_uniq(Users::Email)) -//! .col(string(Users::Password)) -//! .col(string(Users::Name)) -//! .col(string_null(Users::ResetToken)) -//! .col(timestamp_null(Users::ResetSentAt)) -//! .to_owned(); -//! manager.create_table(table).await?; -//! Ok(()) -//! } -//! -//! async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { -//! manager -//! .drop_table(Table::drop().table(Users::Table).to_owned()) -//! .await -//! } -//! } -//! -//! #[derive(Iden)] -//! pub enum Users { -//! Table, -//! Id, -//! Pid, -//! Email, -//! Name, -//! Password, -//! ResetToken, -//! ResetSentAt, -//! } -//! ``` - -use crate::prelude::Iden; -use sea_orm::sea_query::{ - self, Alias, ColumnDef, ColumnType, Expr, IntoIden, PgInterval, Table, TableCreateStatement, -}; - -#[derive(Iden)] -enum GeneralIds { - CreatedAt, - UpdatedAt, -} - -/// Wrapping table schema creation. -pub fn table_auto(name: T) -> TableCreateStatement { - timestamps(Table::create().table(name).if_not_exists().take()) -} - -/// Create a primary key column with auto-increment feature. -pub fn pk_auto(name: T) -> ColumnDef { - integer(name).auto_increment().primary_key().take() -} - -/// Create a UUID primary key -pub fn pk_uuid(name: T) -> ColumnDef { - uuid(name).primary_key().take() -} - -pub fn char_len(col: T, length: u32) -> ColumnDef { - ColumnDef::new(col).char_len(length).not_null().take() -} - -pub fn char_len_null(col: T, length: u32) -> ColumnDef { - ColumnDef::new(col).char_len(length).null().take() -} - -pub fn char_len_uniq(col: T, length: u32) -> ColumnDef { - char_len(col, length).unique_key().take() -} - -pub fn char(col: T) -> ColumnDef { - ColumnDef::new(col).char().not_null().take() -} - -pub fn char_null(col: T) -> ColumnDef { - ColumnDef::new(col).char().null().take() -} - -pub fn char_uniq(col: T) -> ColumnDef { - char(col).unique_key().take() -} - -pub fn string_len(col: T, length: u32) -> ColumnDef { - ColumnDef::new(col).string_len(length).not_null().take() -} - -pub fn string_len_null(col: T, length: u32) -> ColumnDef { - ColumnDef::new(col).string_len(length).null().take() -} - -pub fn string_len_uniq(col: T, length: u32) -> ColumnDef { - string_len(col, length).unique_key().take() -} - -pub fn string(col: T) -> ColumnDef { - ColumnDef::new(col).string().not_null().take() -} - -pub fn string_null(col: T) -> ColumnDef { - ColumnDef::new(col).string().null().take() -} - -pub fn string_uniq(col: T) -> ColumnDef { - string(col).unique_key().take() -} - -pub fn text(col: T) -> ColumnDef { - ColumnDef::new(col).text().not_null().take() -} - -pub fn text_null(col: T) -> ColumnDef { - ColumnDef::new(col).text().null().take() -} - -pub fn text_uniq(col: T) -> ColumnDef { - text(col).unique_key().take() -} - -pub fn tiny_integer(col: T) -> ColumnDef { - ColumnDef::new(col).tiny_integer().not_null().take() -} - -pub fn tiny_integer_null(col: T) -> ColumnDef { - ColumnDef::new(col).tiny_integer().null().take() -} - -pub fn tiny_integer_uniq(col: T) -> ColumnDef { - tiny_integer(col).unique_key().take() -} - -pub fn small_integer(col: T) -> ColumnDef { - ColumnDef::new(col).small_integer().not_null().take() -} - -pub fn small_integer_null(col: T) -> ColumnDef { - ColumnDef::new(col).small_integer().null().take() -} - -pub fn small_integer_uniq(col: T) -> ColumnDef { - small_integer(col).unique_key().take() -} - -pub fn integer(col: T) -> ColumnDef { - ColumnDef::new(col).integer().not_null().take() -} - -pub fn integer_null(col: T) -> ColumnDef { - ColumnDef::new(col).integer().null().take() -} - -pub fn integer_uniq(col: T) -> ColumnDef { - integer(col).unique_key().take() -} - -pub fn big_integer(col: T) -> ColumnDef { - ColumnDef::new(col).big_integer().not_null().take() -} - -pub fn big_integer_null(col: T) -> ColumnDef { - ColumnDef::new(col).big_integer().null().take() -} - -pub fn big_integer_uniq(col: T) -> ColumnDef { - big_integer(col).unique_key().take() -} - -pub fn tiny_unsigned(col: T) -> ColumnDef { - ColumnDef::new(col).tiny_unsigned().not_null().take() -} - -pub fn tiny_unsigned_null(col: T) -> ColumnDef { - ColumnDef::new(col).tiny_unsigned().null().take() -} - -pub fn tiny_unsigned_uniq(col: T) -> ColumnDef { - tiny_unsigned(col).unique_key().take() -} - -pub fn small_unsigned(col: T) -> ColumnDef { - ColumnDef::new(col).small_unsigned().not_null().take() -} - -pub fn small_unsigned_null(col: T) -> ColumnDef { - ColumnDef::new(col).small_unsigned().null().take() -} - -pub fn small_unsigned_uniq(col: T) -> ColumnDef { - small_unsigned(col).unique_key().take() -} - -pub fn unsigned(col: T) -> ColumnDef { - ColumnDef::new(col).unsigned().not_null().take() -} - -pub fn unsigned_null(col: T) -> ColumnDef { - ColumnDef::new(col).unsigned().null().take() -} - -pub fn unsigned_uniq(col: T) -> ColumnDef { - unsigned(col).unique_key().take() -} - -pub fn big_unsigned(col: T) -> ColumnDef { - ColumnDef::new(col).big_unsigned().not_null().take() -} - -pub fn big_unsigned_null(col: T) -> ColumnDef { - ColumnDef::new(col).big_unsigned().null().take() -} - -pub fn big_unsigned_uniq(col: T) -> ColumnDef { - big_unsigned(col).unique_key().take() -} - -pub fn float(col: T) -> ColumnDef { - ColumnDef::new(col).float().not_null().take() -} - -pub fn float_null(col: T) -> ColumnDef { - ColumnDef::new(col).float().null().take() -} - -pub fn float_uniq(col: T) -> ColumnDef { - float(col).unique_key().take() -} - -pub fn double(col: T) -> ColumnDef { - ColumnDef::new(col).double().not_null().take() -} - -pub fn double_null(col: T) -> ColumnDef { - ColumnDef::new(col).double().null().take() -} - -pub fn double_uniq(col: T) -> ColumnDef { - double(col).unique_key().take() -} - -pub fn decimal_len(col: T, precision: u32, scale: u32) -> ColumnDef { - ColumnDef::new(col) - .decimal_len(precision, scale) - .not_null() - .take() -} - -pub fn decimal_len_null(col: T, precision: u32, scale: u32) -> ColumnDef { - ColumnDef::new(col) - .decimal_len(precision, scale) - .null() - .take() -} - -pub fn decimal_len_uniq(col: T, precision: u32, scale: u32) -> ColumnDef { - decimal_len(col, precision, scale).unique_key().take() -} - -pub fn decimal(col: T) -> ColumnDef { - ColumnDef::new(col).decimal().not_null().take() -} - -pub fn decimal_null(col: T) -> ColumnDef { - ColumnDef::new(col).decimal().null().take() -} - -pub fn decimal_uniq(col: T) -> ColumnDef { - decimal(col).unique_key().take() -} - -pub fn date_time(col: T) -> ColumnDef { - ColumnDef::new(col).date_time().not_null().take() -} - -pub fn date_time_null(col: T) -> ColumnDef { - ColumnDef::new(col).date_time().null().take() -} - -pub fn date_time_uniq(col: T) -> ColumnDef { - date_time(col).unique_key().take() -} - -pub fn interval( - col: T, - fields: Option, - precision: Option, -) -> ColumnDef { - ColumnDef::new(col) - .interval(fields, precision) - .not_null() - .take() -} - -pub fn interval_null( - col: T, - fields: Option, - precision: Option, -) -> ColumnDef { - ColumnDef::new(col) - .interval(fields, precision) - .null() - .take() -} - -pub fn interval_uniq( - col: T, - fields: Option, - precision: Option, -) -> ColumnDef { - interval(col, fields, precision).unique_key().take() -} - -pub fn timestamp(col: T) -> ColumnDef { - ColumnDef::new(col).timestamp().not_null().take() -} - -pub fn timestamp_null(col: T) -> ColumnDef { - ColumnDef::new(col).timestamp().null().take() -} - -pub fn timestamp_uniq(col: T) -> ColumnDef { - timestamp(col).unique_key().take() -} - -pub fn timestamp_with_time_zone(col: T) -> ColumnDef { - ColumnDef::new(col) - .timestamp_with_time_zone() - .not_null() - .take() -} - -pub fn timestamp_with_time_zone_null(col: T) -> ColumnDef { - ColumnDef::new(col).timestamp_with_time_zone().null().take() -} - -pub fn timestamp_with_time_zone_uniq(col: T) -> ColumnDef { - timestamp_with_time_zone(col).unique_key().take() -} - -pub fn time(col: T) -> ColumnDef { - ColumnDef::new(col).time().not_null().take() -} - -pub fn time_null(col: T) -> ColumnDef { - ColumnDef::new(col).time().null().take() -} - -pub fn time_uniq(col: T) -> ColumnDef { - time(col).unique_key().take() -} - -pub fn date(col: T) -> ColumnDef { - ColumnDef::new(col).date().not_null().take() -} - -pub fn date_null(col: T) -> ColumnDef { - ColumnDef::new(col).date().null().take() -} - -pub fn date_uniq(col: T) -> ColumnDef { - date(col).unique_key().take() -} - -pub fn year(col: T) -> ColumnDef { - ColumnDef::new(col).year().not_null().take() -} - -pub fn year_null(col: T) -> ColumnDef { - ColumnDef::new(col).year().null().take() -} - -pub fn year_uniq(col: T) -> ColumnDef { - year(col).unique_key().take() -} - -pub fn binary_len(col: T, length: u32) -> ColumnDef { - ColumnDef::new(col).binary_len(length).not_null().take() -} - -pub fn binary_len_null(col: T, length: u32) -> ColumnDef { - ColumnDef::new(col).binary_len(length).null().take() -} - -pub fn binary_len_uniq(col: T, length: u32) -> ColumnDef { - binary_len(col, length).unique_key().take() -} - -pub fn binary(col: T) -> ColumnDef { - ColumnDef::new(col).binary().not_null().take() -} - -pub fn binary_null(col: T) -> ColumnDef { - ColumnDef::new(col).binary().null().take() -} - -pub fn binary_uniq(col: T) -> ColumnDef { - binary(col).unique_key().take() -} - -pub fn var_binary(col: T, length: u32) -> ColumnDef { - ColumnDef::new(col).var_binary(length).not_null().take() -} - -pub fn var_binary_null(col: T, length: u32) -> ColumnDef { - ColumnDef::new(col).var_binary(length).null().take() -} - -pub fn var_binary_uniq(col: T, length: u32) -> ColumnDef { - var_binary(col, length).unique_key().take() -} - -pub fn bit(col: T, length: Option) -> ColumnDef { - ColumnDef::new(col).bit(length).not_null().take() -} - -pub fn bit_null(col: T, length: Option) -> ColumnDef { - ColumnDef::new(col).bit(length).null().take() -} - -pub fn bit_uniq(col: T, length: Option) -> ColumnDef { - bit(col, length).unique_key().take() -} - -pub fn varbit(col: T, length: u32) -> ColumnDef { - ColumnDef::new(col).varbit(length).not_null().take() -} - -pub fn varbit_null(col: T, length: u32) -> ColumnDef { - ColumnDef::new(col).varbit(length).null().take() -} - -pub fn varbit_uniq(col: T, length: u32) -> ColumnDef { - varbit(col, length).unique_key().take() -} - -pub fn blob(col: T) -> ColumnDef { - ColumnDef::new(col).blob().not_null().take() -} - -pub fn blob_null(col: T) -> ColumnDef { - ColumnDef::new(col).blob().null().take() -} - -pub fn blob_uniq(col: T) -> ColumnDef { - blob(col).unique_key().take() -} - -pub fn boolean(col: T) -> ColumnDef { - ColumnDef::new(col).boolean().not_null().take() -} - -pub fn boolean_null(col: T) -> ColumnDef { - ColumnDef::new(col).boolean().null().take() -} - -pub fn boolean_uniq(col: T) -> ColumnDef { - boolean(col).unique_key().take() -} - -pub fn money_len(col: T, precision: u32, scale: u32) -> ColumnDef { - ColumnDef::new(col) - .money_len(precision, scale) - .not_null() - .take() -} - -pub fn money_len_null(col: T, precision: u32, scale: u32) -> ColumnDef { - ColumnDef::new(col) - .money_len(precision, scale) - .null() - .take() -} - -pub fn money_len_uniq(col: T, precision: u32, scale: u32) -> ColumnDef { - money_len(col, precision, scale).unique_key().take() -} - -pub fn money(col: T) -> ColumnDef { - ColumnDef::new(col).money().not_null().take() -} - -pub fn money_null(col: T) -> ColumnDef { - ColumnDef::new(col).money().null().take() -} - -pub fn money_uniq(col: T) -> ColumnDef { - money(col).unique_key().take() -} - -pub fn json(col: T) -> ColumnDef { - ColumnDef::new(col).json().not_null().take() -} - -pub fn json_null(col: T) -> ColumnDef { - ColumnDef::new(col).json().null().take() -} - -pub fn json_uniq(col: T) -> ColumnDef { - json(col).unique_key().take() -} - -pub fn json_binary(col: T) -> ColumnDef { - ColumnDef::new(col).json_binary().not_null().take() -} - -pub fn json_binary_null(col: T) -> ColumnDef { - ColumnDef::new(col).json_binary().null().take() -} - -pub fn json_binary_uniq(col: T) -> ColumnDef { - json_binary(col).unique_key().take() -} - -pub fn uuid(col: T) -> ColumnDef { - ColumnDef::new(col).uuid().not_null().take() -} - -pub fn uuid_null(col: T) -> ColumnDef { - ColumnDef::new(col).uuid().null().take() -} - -pub fn uuid_uniq(col: T) -> ColumnDef { - uuid(col).unique_key().take() -} - -pub fn custom(col: T, name: N) -> ColumnDef { - ColumnDef::new(col).custom(name).not_null().take() -} - -pub fn custom_null(col: T, name: N) -> ColumnDef { - ColumnDef::new(col).custom(name).null().take() -} - -pub fn enumeration(col: T, name: N, variants: V) -> ColumnDef -where - T: IntoIden, - N: IntoIden, - S: IntoIden, - V: IntoIterator, -{ - ColumnDef::new(col) - .enumeration(name, variants) - .not_null() - .take() -} - -pub fn enumeration_null(col: T, name: N, variants: V) -> ColumnDef -where - T: IntoIden, - N: IntoIden, - S: IntoIden, - V: IntoIterator, -{ - ColumnDef::new(col) - .enumeration(name, variants) - .null() - .take() -} - -pub fn enumeration_uniq(col: T, name: N, variants: V) -> ColumnDef -where - T: IntoIden, - N: IntoIden, - S: IntoIden, - V: IntoIterator, -{ - enumeration(col, name, variants).unique_key().take() -} - -pub fn array(col: T, elem_type: ColumnType) -> ColumnDef { - ColumnDef::new(col).array(elem_type).not_null().take() -} - -pub fn array_null(col: T, elem_type: ColumnType) -> ColumnDef { - ColumnDef::new(col).array(elem_type).null().take() -} - -pub fn array_uniq(col: T, elem_type: ColumnType) -> ColumnDef { - array(col, elem_type).unique_key().take() -} - -/// Add timestamp columns (`CreatedAt` and `UpdatedAt`) to an existing table. -pub fn timestamps(t: TableCreateStatement) -> TableCreateStatement { - let mut t = t; - t.col(timestamp(GeneralIds::CreatedAt).default(Expr::current_timestamp())) - .col(timestamp(GeneralIds::UpdatedAt).default(Expr::current_timestamp())) - .take() -} - -/// Create an Alias. -pub fn name>(name: T) -> Alias { - Alias::new(name) -} diff --git a/packages/pagetop-seaorm/src/db/migration/seaql_migrations.rs b/packages/pagetop-seaorm/src/db/migration/seaql_migrations.rs deleted file mode 100644 index 51da9300..00000000 --- a/packages/pagetop-seaorm/src/db/migration/seaql_migrations.rs +++ /dev/null @@ -1,15 +0,0 @@ -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] -// One should override the name of migration table via `MigratorTrait::migration_table_name` method -#[sea_orm(table_name = "seaql_migrations")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub version: String, - pub applied_at: i64, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/packages/pagetop-seaorm/src/lib.rs b/packages/pagetop-seaorm/src/lib.rs deleted file mode 100644 index 75c524a7..00000000 --- a/packages/pagetop-seaorm/src/lib.rs +++ /dev/null @@ -1,31 +0,0 @@ -use pagetop::prelude::*; - -use std::sync::LazyLock; - -pub mod config; -pub mod db; - -/// The package Prelude. -pub mod prelude { - pub use crate::db::*; - pub use crate::install_migrations; -} - -include_locales!(LOCALES_SEAORM); - -/// Implements [`PackageTrait`] and specific package API. -pub struct SeaORM; - -impl PackageTrait for SeaORM { - fn name(&self) -> L10n { - L10n::t("package_name", &LOCALES_SEAORM) - } - - fn description(&self) -> L10n { - L10n::t("package_description", &LOCALES_SEAORM) - } - - fn init(&self) { - LazyLock::force(&db::DBCONN); - } -} diff --git a/packages/pagetop-seaorm/src/locale/en-US/package.ftl b/packages/pagetop-seaorm/src/locale/en-US/package.ftl deleted file mode 100644 index d1c53cc1..00000000 --- a/packages/pagetop-seaorm/src/locale/en-US/package.ftl +++ /dev/null @@ -1,2 +0,0 @@ -package_name = SeaORM support -package_description = Integrate SeaORM as the database framework for PageTop applications. diff --git a/packages/pagetop-seaorm/src/locale/es-ES/package.ftl b/packages/pagetop-seaorm/src/locale/es-ES/package.ftl deleted file mode 100644 index bd7c004b..00000000 --- a/packages/pagetop-seaorm/src/locale/es-ES/package.ftl +++ /dev/null @@ -1,2 +0,0 @@ -package_name = Soporte a SeaORM -package_description = Integra SeaORM como framework de base de datos para aplicaciones PageTop. diff --git a/pagetop/Cargo.toml b/packages/pagetop/Cargo.toml similarity index 62% rename from pagetop/Cargo.toml rename to packages/pagetop/Cargo.toml index d5fed213..67d8f12c 100644 --- a/pagetop/Cargo.toml +++ b/packages/pagetop/Cargo.toml @@ -8,7 +8,7 @@ description = """\ """ categories = ["web-programming", "gui", "development-tools", "asynchronous"] keywords = ["pagetop", "web", "framework", "frontend", "ssr"] -readme = "../README.md" +readme = "../../README.md" homepage = { workspace = true } repository = { workspace = true } @@ -19,24 +19,22 @@ license = { workspace = true } 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" +colored = "2.1.0" +concat-string = "1.0.1" +figlet-rs = "0.1.5" +fluent-bundle = "0.15.3" +fluent-templates = "0.11.0" +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"] } +tracing-actix-web = "0.7.15" +unic-langid = { version = "0.9.5", features = ["macros"] } actix-web = "4.9.0" actix-web-files = { package = "actix-files", version = "0.6.6" } @@ -47,6 +45,3 @@ serde.workspace = true static-files.workspace = true pagetop-macros.workspace = true - -[build-dependencies] -pagetop-build.workspace = true diff --git a/pagetop/src/app.rs b/packages/pagetop/src/app.rs similarity index 93% rename from pagetop/src/app.rs rename to packages/pagetop/src/app.rs index aaac3234..34e1c373 100644 --- a/pagetop/src/app.rs +++ b/packages/pagetop/src/app.rs @@ -3,8 +3,6 @@ mod figfont; use crate::core::{package, package::PackageRef}; -use crate::html::Markup; -use crate::response::page::{ErrorPage, ResultPage}; use crate::{global, locale, service, trace}; use actix_session::config::{BrowserSession, PersistentSession, SessionLifecycle}; @@ -44,7 +42,7 @@ impl Application { LazyLock::force(&trace::TRACING); // Validates the default language identifier. - LazyLock::force(&locale::DEFAULT_LANGID); + LazyLock::force(&locale::LANGID_DEFAULT); // Registers the application's packages. package::all::register_packages(root_package); @@ -156,12 +154,12 @@ impl Application { InitError = (), >, > { - service::App::new() - .configure(package::all::configure_services) - .default_service(service::web::route().to(service_not_found)) + service::App::new().configure(package::all::configure_services) + // .default_service(service::web::route().to(service_not_found)) } } - -async fn service_not_found(request: service::HttpRequest) -> ResultPage { +/* +async fn service_not_found(request: HttpRequest) -> ResultPage { Err(ErrorPage::NotFound(request)) } +*/ diff --git a/pagetop/src/app/figfont.rs b/packages/pagetop/src/app/figfont.rs similarity index 100% rename from pagetop/src/app/figfont.rs rename to packages/pagetop/src/app/figfont.rs diff --git a/pagetop/src/app/slant.flf b/packages/pagetop/src/app/slant.flf similarity index 100% rename from pagetop/src/app/slant.flf rename to packages/pagetop/src/app/slant.flf diff --git a/pagetop/src/app/small.flf b/packages/pagetop/src/app/small.flf similarity index 100% rename from pagetop/src/app/small.flf rename to packages/pagetop/src/app/small.flf diff --git a/pagetop/src/app/speed.flf b/packages/pagetop/src/app/speed.flf similarity index 100% rename from pagetop/src/app/speed.flf rename to packages/pagetop/src/app/speed.flf diff --git a/pagetop/src/app/starwars.flf b/packages/pagetop/src/app/starwars.flf similarity index 100% rename from pagetop/src/app/starwars.flf rename to packages/pagetop/src/app/starwars.flf diff --git a/pagetop/src/core.rs b/packages/pagetop/src/core.rs similarity index 100% rename from pagetop/src/core.rs rename to packages/pagetop/src/core.rs diff --git a/pagetop/src/core/action.rs b/packages/pagetop/src/core/action.rs similarity index 100% rename from pagetop/src/core/action.rs rename to packages/pagetop/src/core/action.rs diff --git a/pagetop/src/core/action/all.rs b/packages/pagetop/src/core/action/all.rs similarity index 100% rename from pagetop/src/core/action/all.rs rename to packages/pagetop/src/core/action/all.rs diff --git a/pagetop/src/core/action/definition.rs b/packages/pagetop/src/core/action/definition.rs similarity index 100% rename from pagetop/src/core/action/definition.rs rename to packages/pagetop/src/core/action/definition.rs diff --git a/pagetop/src/core/action/list.rs b/packages/pagetop/src/core/action/list.rs similarity index 100% rename from pagetop/src/core/action/list.rs rename to packages/pagetop/src/core/action/list.rs diff --git a/pagetop/src/core/package.rs b/packages/pagetop/src/core/package.rs similarity index 77% rename from pagetop/src/core/package.rs rename to packages/pagetop/src/core/package.rs index 69be7b7b..e0c6a12e 100644 --- a/pagetop/src/core/package.rs +++ b/packages/pagetop/src/core/package.rs @@ -2,4 +2,3 @@ mod definition; pub use definition::{PackageRef, PackageTrait}; pub(crate) mod all; -pub(crate) mod welcome; diff --git a/pagetop/src/core/package/all.rs b/packages/pagetop/src/core/package/all.rs similarity index 90% rename from pagetop/src/core/package/all.rs rename to packages/pagetop/src/core/package/all.rs index c21fed8f..fd177c66 100644 --- a/pagetop/src/core/package/all.rs +++ b/packages/pagetop/src/core/package/all.rs @@ -1,7 +1,7 @@ use crate::core::action::add_action; -use crate::core::package::{welcome, PackageRef}; +use crate::core::package::PackageRef; use crate::core::theme::all::THEMES; -use crate::{include_files, include_files_service, service, trace}; +use crate::{service, trace}; use std::sync::{LazyLock, RwLock}; @@ -23,6 +23,8 @@ pub fn register_packages(root_package: Option) { 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); @@ -39,14 +41,16 @@ pub fn register_packages(root_package: Option) { fn add_to_enabled(list: &mut Vec, 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 dependencies in reverse order first. - for d in package.dependencies().iter().rev() { + // 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); } - // Add the package itself to the enabled list. - list.push(package); - // Check if the package has an associated theme to register. if let Some(theme) = package.theme() { let mut registered_themes = THEMES.write().unwrap(); @@ -115,14 +119,8 @@ pub fn init_packages() { // CONFIGURE SERVICES ****************************************************************************** -include_files!(assets); - 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)); - // Default assets. - include_files_service!(scfg, assets => "/"); } diff --git a/pagetop/src/core/package/definition.rs b/packages/pagetop/src/core/package/definition.rs similarity index 97% rename from pagetop/src/core/package/definition.rs rename to packages/pagetop/src/core/package/definition.rs index b1d0a8c9..3506a929 100644 --- a/pagetop/src/core/package/definition.rs +++ b/packages/pagetop/src/core/package/definition.rs @@ -13,7 +13,7 @@ pub trait PackageTrait: AnyBase + Send + Sync { } fn description(&self) -> L10n { - L10n::default() + L10n::none() } fn theme(&self) -> Option { diff --git a/pagetop/src/core/theme.rs b/packages/pagetop/src/core/theme.rs similarity index 100% rename from pagetop/src/core/theme.rs rename to packages/pagetop/src/core/theme.rs diff --git a/pagetop/src/core/theme/all.rs b/packages/pagetop/src/core/theme/all.rs similarity index 79% rename from pagetop/src/core/theme/all.rs rename to packages/pagetop/src/core/theme/all.rs index b27bf1e5..7aab35a7 100644 --- a/pagetop/src/core/theme/all.rs +++ b/packages/pagetop/src/core/theme/all.rs @@ -1,6 +1,4 @@ -use crate::core::package::PackageTrait; -use crate::core::theme::{ThemeRef, ThemeTrait}; -use crate::global; +use crate::core::theme::ThemeRef; use std::sync::{LazyLock, RwLock}; @@ -8,7 +6,7 @@ use std::sync::{LazyLock, RwLock}; pub static THEMES: LazyLock>> = LazyLock::new(|| RwLock::new(Vec::new())); -// DEFAULT THEME *********************************************************************************** +/* DEFAULT THEME *********************************************************************************** pub struct NoTheme; @@ -18,9 +16,10 @@ impl PackageTrait for NoTheme { } } -impl ThemeTrait for NoTheme {} +impl ThemeTrait for NoTheme { +} -pub static DEFAULT_THEME: LazyLock = +pub static THEME_DEFAULT: LazyLock = LazyLock::new(|| match theme_by_short_name(&global::SETTINGS.app.theme) { Some(theme) => theme, None => &NoTheme, @@ -40,3 +39,4 @@ pub fn theme_by_short_name(short_name: &str) -> Option { _ => None, } } +*/ diff --git a/packages/pagetop/src/core/theme/definition.rs b/packages/pagetop/src/core/theme/definition.rs new file mode 100644 index 00000000..70c178f2 --- /dev/null +++ b/packages/pagetop/src/core/theme/definition.rs @@ -0,0 +1,104 @@ +use crate::core::package::PackageTrait; + +pub type ThemeRef = &'static dyn ThemeTrait; + +/// Los temas deben implementar este "trait". +pub trait ThemeTrait: PackageTrait + Send + Sync { + /* + #[rustfmt::skip] + fn regions(&self) -> Vec<(&'static str, L10n)> { + vec![ + ("header", L10n::l("header")), + ("pagetop", L10n::l("pagetop")), + ("sidebar_left", L10n::l("sidebar_left")), + ("content", L10n::l("content")), + ("sidebar_right", L10n::l("sidebar_right")), + ("footer", L10n::l("footer")), + ] + } + + #[allow(unused_variables)] + fn before_prepare_body(&self, page: &mut Page) {} + + fn prepare_body(&self, page: &mut Page) -> PrepareMarkup { + let skip_to_id = page.body_skip_to().get().unwrap_or("content".to_owned()); + + PrepareMarkup::With(html! { + body id=[page.body_id().get()] class=[page.body_classes().get()] { + @if let Some(skip) = L10n::l("skip_to_content").using(page.context().langid()) { + div class="skip__to_content" { + a href=(concat_string!("#", skip_to_id)) { (skip) } + } + } + (flex::Container::new() + .with_id("body__wrapper") + .with_direction(flex::Direction::Column(BreakPoint::None)) + .with_align(flex::Align::Center) + .add_item(flex::Item::region().with_id("header")) + .add_item(flex::Item::region().with_id("pagetop")) + .add_item( + flex::Item::with( + flex::Container::new() + .with_direction(flex::Direction::Row(BreakPoint::None)) + .add_item( + flex::Item::region() + .with_id("sidebar_left") + .with_grow(flex::Grow::Is1), + ) + .add_item( + flex::Item::region() + .with_id("content") + .with_grow(flex::Grow::Is3), + ) + .add_item( + flex::Item::region() + .with_id("sidebar_right") + .with_grow(flex::Grow::Is1), + ), + ) + .with_id("flex__wrapper"), + ) + .add_item(flex::Item::region().with_id("footer")) + .render(page.context())) + } + }) + } + + fn after_prepare_body(&self, page: &mut Page) { + page.set_assets(AssetsOp::SetFaviconIfNone( + Favicon::new().with_icon("/base/favicon.ico"), + )); + } + + fn prepare_head(&self, page: &mut Page) -> PrepareMarkup { + let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no"; + PrepareMarkup::With(html! { + head { + meta charset="utf-8"; + + @if let Some(title) = page.title() { + title { (global::SETTINGS.app.name) (" - ") (title) } + } @else { + title { (global::SETTINGS.app.name) } + } + + @if let Some(description) = page.description() { + meta name="description" content=(description); + } + + meta name="viewport" content=(viewport); + @for (name, content) in page.metadata() { + meta name=(name) content=(content) {} + } + + meta http-equiv="X-UA-Compatible" content="IE=edge"; + @for (property, content) in page.properties() { + meta property=(property) content=(content) {} + } + + (page.context().prepare_assets()) + } + }) + } + */ +} diff --git a/pagetop/src/global.rs b/packages/pagetop/src/global.rs similarity index 98% rename from pagetop/src/global.rs rename to packages/pagetop/src/global.rs index e538958b..49c0cdba 100644 --- a/pagetop/src/global.rs +++ b/packages/pagetop/src/global.rs @@ -1,10 +1,10 @@ //! Global settings. -use crate::include_config; +use crate::static_config; use serde::Deserialize; -include_config!(SETTINGS: Settings => [ +static_config!(SETTINGS: Settings => [ // [app] "app.name" => "My App", "app.description" => "Developed with the amazing PageTop framework.", diff --git a/packages/pagetop/src/html.rs b/packages/pagetop/src/html.rs new file mode 100644 index 00000000..f81273bf --- /dev/null +++ b/packages/pagetop/src/html.rs @@ -0,0 +1,4 @@ +//! HTML in code. + +mod maud; +pub use maud::{html, html_private, Markup, PreEscaped, DOCTYPE}; diff --git a/pagetop/src/html/maud.rs b/packages/pagetop/src/html/maud.rs similarity index 100% rename from pagetop/src/html/maud.rs rename to packages/pagetop/src/html/maud.rs diff --git a/pagetop/src/html/maud/escape.rs b/packages/pagetop/src/html/maud/escape.rs similarity index 100% rename from pagetop/src/html/maud/escape.rs rename to packages/pagetop/src/html/maud/escape.rs diff --git a/pagetop/src/lib.rs b/packages/pagetop/src/lib.rs similarity index 94% rename from pagetop/src/lib.rs rename to packages/pagetop/src/lib.rs index 4076b92f..cbda48c5 100644 --- a/pagetop/src/lib.rs +++ b/packages/pagetop/src/lib.rs @@ -42,7 +42,7 @@ //! //! async fn hello_world(request: HttpRequest) -> ResultPage { //! Page::new(request) -//! .with_body(PrepareMarkup::With(html! { h1 { "Hello World!" } })) +//! .with_component(Html::with(html! { h1 { "Hello World!" } })) //! .render() //! } //! @@ -53,7 +53,7 @@ //! ``` //! 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](`global::Server`)). You can find this code in the `PageTop` +//! 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 @@ -79,7 +79,7 @@ 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::{fn_builder, html, main, test, AutoDefault}; +pub use pagetop_macros::{main, test, AutoDefault}; pub type StaticResources = std::collections::HashMap<&'static str, static_files::Resource>; @@ -91,8 +91,6 @@ pub type Weight = i8; // Useful functions and macros. pub mod util; -// Load configuration settings. -pub mod config; // Application tracing and event logging. pub mod trace; // HTML in code. diff --git a/pagetop/src/locale.rs b/packages/pagetop/src/locale.rs similarity index 64% rename from pagetop/src/locale.rs rename to packages/pagetop/src/locale.rs index ef9c1924..80b366b3 100644 --- a/pagetop/src/locale.rs +++ b/packages/pagetop/src/locale.rs @@ -67,13 +67,13 @@ //! # How to apply localization in your code //! //! Once you have created your FTL resource directory, use the -//! [`include_locales!`](crate::include_locales) macro to integrate them into your module or +//! [`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::*; //! -//! include_locales!(LOCALES_SAMPLE); +//! static_locales!(LOCALES_SAMPLE); //! ``` //! //! But if they are in another directory, then you can use: @@ -81,15 +81,14 @@ //! ``` //! use pagetop::prelude::*; //! -//! include_locales!(LOCALES_SAMPLE from "path/to/locale"); +//! 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::{CharacterDirection, LanguageIdentifier}; +pub use unic_langid::LanguageIdentifier; use fluent_templates::Loader; use fluent_templates::StaticLoader as Locales; @@ -101,62 +100,43 @@ 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 locale key names. +/// and human-readable names. static LANGUAGES: LazyLock> = 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" ), + "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)"), ] }); -static FALLBACK: LazyLock = LazyLock::new(|| langid!("en-US")); +pub static LANGID_FALLBACK: LazyLock = 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(&FALLBACK)); +pub static LANGID_DEFAULT: LazyLock<&LanguageIdentifier> = + LazyLock::new(|| langid_for(&global::SETTINGS.app.language).unwrap_or(&LANGID_FALLBACK)); -pub enum LangError { - EmptyLang, - UnknownLang(String), -} - -impl fmt::Display for LangError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LangError::EmptyLang => write!(f, "The language identifier is empty."), - LangError::UnknownLang(lang) => write!(f, "Unknown language identifier: {lang}"), - } - } -} - -pub fn langid_for(language: impl Into) -> Result<&'static LanguageIdentifier, LangError> { +pub fn langid_for(language: impl Into) -> Result<&'static LanguageIdentifier, String> { let language = language.into(); if language.is_empty() { - return Err(LangError::EmptyLang); + return Ok(&LANGID_FALLBACK); } - // Attempt to match the full language code (e.g., "es-MX"). - if let Some(langid) = LANGUAGES.get(&language).map(|(langid, _)| langid) { - return Ok(langid); - } - // Fallback to the base language if no sublocale is found (e.g., "es"). - if let Some((base_lang, _)) = language.split_once('-') { - if let Some(langid) = LANGUAGES.get(base_lang).map(|(langid, _)| langid) { - return Ok(langid); - } - } - Err(LangError::UnknownLang(language)) + 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! include_locales { +macro_rules! static_locales { ( $LOCALES:ident $(, $core_locales:literal)? ) => { $crate::locale::fluent_templates::static_loader! { static $LOCALES = { @@ -168,7 +148,7 @@ macro_rules! include_locales { }; } }; - ( $LOCALES:ident from $dir_locales:literal $(, $core_locales:literal)? ) => { + ( $LOCALES:ident in $dir_locales:literal $(, $core_locales:literal)? ) => { $crate::locale::fluent_templates::static_loader! { static $LOCALES = { locales: $dir_locales, @@ -181,7 +161,7 @@ macro_rules! include_locales { }; } -include_locales!(LOCALES_PAGETOP); +static_locales!(LOCALES_PAGETOP); #[derive(AutoDefault)] enum L10nOp { @@ -194,15 +174,18 @@ enum L10nOp { #[derive(AutoDefault)] pub struct L10n { op: L10nOp, - #[default(&LOCALES_PAGETOP)] - locales: &'static Locales, + locales: Option<&'static Locales>, args: HashMap>, } impl L10n { + pub fn none() -> Self { + L10n::default() + } + pub fn n(text: impl Into) -> Self { L10n { - op: L10nOp::Text(text.into().to_string()), + op: L10nOp::Text(text.into()), ..Default::default() } } @@ -210,6 +193,7 @@ impl L10n { pub fn l(key: impl Into) -> Self { L10n { op: L10nOp::Translate(key.into()), + locales: Some(&LOCALES_PAGETOP), ..Default::default() } } @@ -217,60 +201,68 @@ impl L10n { pub fn t(key: impl Into, locales: &'static Locales) -> Self { L10n { op: L10nOp::Translate(key.into()), - locales, + locales: Some(locales), ..Default::default() } } pub fn with_arg(mut self, arg: impl Into, value: impl Into) -> Self { - let value = FluentValue::from(value.into()); - self.args.insert(arg.into(), value); + self.args + .insert(arg.into(), FluentValue::from(value.into())); self } - pub fn with_args(mut self, args: HashMap) -> Self { - for (k, v) in args { - self.args.insert(k, FluentValue::from(v)); - } - self - } - - pub fn get(&self) -> Option { - self.using(&DEFAULT_LANGID) - } - pub fn using(&self, langid: &LanguageIdentifier) -> Option { match &self.op { L10nOp::None => None, L10nOp::Text(text) => Some(text.to_owned()), - L10nOp::Translate(key) => { - if self.args.is_empty() { - self.locales.try_lookup(langid, key) - } else { - self.locales.try_lookup_with_args(langid, key, &self.args) + 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 translated text using the default language identifier. - pub fn markup(&self) -> Markup { - PreEscaped(self.get().unwrap_or_default()) - } - - /// Escapes translated text using the specified language identifier. - pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup { - PreEscaped(self.using(langid).unwrap_or_default()) - } } impl fmt::Display for L10n { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let content = match &self.op { - L10nOp::None => "".to_string(), - L10nOp::Text(text) => text.clone(), - L10nOp::Translate(key) => self.get().unwrap_or_else(|| format!("No <{}>", key)), - }; - write!(f, "{content}") + 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, + _ => &LANGID_DEFAULT, + }, + key, + ) + } else { + locales.lookup_with_args( + match key.as_str() { + LANGUAGE_SET_FAILURE => &LANGID_FALLBACK, + _ => &LANGID_DEFAULT, + }, + key, + &self.args, + ) + } + ) + } else { + write!(f, "Unknown localization {key}") + } + } + } } } diff --git a/packages/pagetop/src/locale/en-US/base.ftl b/packages/pagetop/src/locale/en-US/base.ftl new file mode 100644 index 00000000..9ec4803b --- /dev/null +++ b/packages/pagetop/src/locale/en-US/base.ftl @@ -0,0 +1,13 @@ +# Branding component. +site_home = Home + +# PoweredBy component. +poweredby_pagetop = Powered by {$pagetop_link} +pagetop_logo = PageTop logo + +# Menu component. +menu_toggle = Toggle menu visibility + +# Form components. +button_submit = Submit +button_reset = Reset diff --git a/packages/pagetop/src/locale/en-US/theme.ftl b/packages/pagetop/src/locale/en-US/theme.ftl new file mode 100644 index 00000000..6b3cb0e8 --- /dev/null +++ b/packages/pagetop/src/locale/en-US/theme.ftl @@ -0,0 +1,8 @@ +header = Header +pagetop = Page Top +content = Content +sidebar_left = Sidebar Left +sidebar_right = Sidebar Right +footer = Footer + +skip_to_content = Skip to main content (Press Enter) diff --git a/packages/pagetop/src/locale/en-US/welcome.ftl b/packages/pagetop/src/locale/en-US/welcome.ftl new file mode 100644 index 00000000..566691e4 --- /dev/null +++ b/packages/pagetop/src/locale/en-US/welcome.ftl @@ -0,0 +1,26 @@ +welcome_package_name = Default homepage +welcome_package_description = Displays a demo homepage when none is configured. + +welcome_title = Hello world! + +welcome_intro = This page is used to check the proper operation of the { $app } installation. +welcome_powered = This web solution is powered by { $pagetop }. +welcome_code = Code +welcome = Welcome + +welcome_page = Welcome page +welcome_subtitle = Are you user of { $app }? +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 problem persists, please contact your system administrator. + +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 Rust-based web development framework to build modular, extensible, and configurable web solutions. +welcome_pagetop_text3 = For more information on PageTop please visit the technical documentation. + +welcome_promo_title = Promoting PageTop +welcome_promo_text1 = You are free to use the image below on applications powered by { $pagetop }. Thanks for using PageTop! + +welcome_issues_title = Reporting problems +welcome_issues_text1 = Please use GitHub to report any issues with PageTop. However, check the existing error reports before submitting a new issue. +welcome_issues_text2 = If the issues are specific to { $app }, please refer to its official repository or support channel, rather than directly to PageTop. diff --git a/packages/pagetop/src/locale/es-ES/base.ftl b/packages/pagetop/src/locale/es-ES/base.ftl new file mode 100644 index 00000000..953d891c --- /dev/null +++ b/packages/pagetop/src/locale/es-ES/base.ftl @@ -0,0 +1,13 @@ +# Branding component. +site_home = Inicio + +# PoweredBy component. +poweredby_pagetop = Funciona con {$pagetop_link} +pagetop_logo = Logotipo de PageTop + +# Menu component. +menu_toggle = Alternar visibilidad del menú + +# Form components. +button_submit = Enviar +button_reset = Reiniciar diff --git a/packages/pagetop/src/locale/es-ES/theme.ftl b/packages/pagetop/src/locale/es-ES/theme.ftl new file mode 100644 index 00000000..fb5caacd --- /dev/null +++ b/packages/pagetop/src/locale/es-ES/theme.ftl @@ -0,0 +1,8 @@ +header = Cabecera +pagetop = Superior +content = Contenido +sidebar_left = Barra lateral izquierda +sidebar_right = Barra lateral derecha +footer = Pie + +skip_to_content = Ir al contenido principal (Pulsar Intro) diff --git a/packages/pagetop/src/locale/es-ES/welcome.ftl b/packages/pagetop/src/locale/es-ES/welcome.ftl new file mode 100644 index 00000000..5034f155 --- /dev/null +++ b/packages/pagetop/src/locale/es-ES/welcome.ftl @@ -0,0 +1,26 @@ +welcome_package_name = Página de inicio predeterminada +welcome_package_description = Muestra una página de demostración predeterminada cuando no hay ninguna configurada. + +welcome_title = ¡Hola mundo! + +welcome_intro = Esta página se utiliza para verificar el correcto funcionamiento de la instalación de { $app }. +welcome_powered = Esta solución web funciona con { $pagetop }. +welcome_code = Código +welcome = Bienvenida + +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, 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 Rust para construir soluciones web modulares, extensibles y configurables. +welcome_pagetop_text3 = Para más información sobre PageTop, por favor visita la documentación técnica. + +welcome_promo_title = Promociona PageTop +welcome_promo_text1 = Eres libre de usar la siguiente imagen en aplicaciones desarrolladas con { $pagetop }. ¡Gracias por usar PageTop! + +welcome_issues_title = Informando problemas +welcome_issues_text1 = Por favor, utiliza GitHub para reportar cualquier problema con PageTop. No obstante, comprueba los informes de errores existentes antes de enviar uno nuevo. +welcome_issues_text2 = Si son fallos específicos de { $app }, por favor acude a su repositorio o canal de soporte oficial y no al de PageTop directamente. diff --git a/pagetop/src/prelude.rs b/packages/pagetop/src/prelude.rs similarity index 66% rename from pagetop/src/prelude.rs rename to packages/pagetop/src/prelude.rs index c1489152..76c29b03 100644 --- a/pagetop/src/prelude.rs +++ b/packages/pagetop/src/prelude.rs @@ -2,20 +2,18 @@ // RE-EXPORTED. -pub use crate::{concat_string, fn_builder, html, main, paste, test}; +pub use crate::{concat_string, main, paste, test}; pub use crate::{AutoDefault, StaticResources, TypeId, Weight}; // MACROS. // crate::util -pub use crate::kv; -// crate::config -pub use crate::include_config; +pub use crate::{kv, static_config}; // crate::locale -pub use crate::include_locales; +pub use crate::static_locales; // crate::service -pub use crate::{include_files, include_files_service}; +pub use crate::{static_files, static_files_service}; // crate::core::action pub use crate::actions; @@ -38,7 +36,7 @@ pub use crate::core::action::*; pub use crate::core::package::*; pub use crate::core::theme::*; -pub use crate::response::{json::*, page::*, redirect::*, ResponseError}; +pub use crate::response::{json::*, redirect::*, ResponseError}; pub use crate::global; diff --git a/pagetop/src/response.rs b/packages/pagetop/src/response.rs similarity index 87% rename from pagetop/src/response.rs rename to packages/pagetop/src/response.rs index e51974b1..ecbcd954 100644 --- a/pagetop/src/response.rs +++ b/packages/pagetop/src/response.rs @@ -2,8 +2,6 @@ pub use actix_web::ResponseError; -pub mod page; - pub mod json; pub mod redirect; diff --git a/pagetop/src/response/json.rs b/packages/pagetop/src/response/json.rs similarity index 100% rename from pagetop/src/response/json.rs rename to packages/pagetop/src/response/json.rs diff --git a/pagetop/src/response/redirect.rs b/packages/pagetop/src/response/redirect.rs similarity index 100% rename from pagetop/src/response/redirect.rs rename to packages/pagetop/src/response/redirect.rs diff --git a/pagetop/src/service.rs b/packages/pagetop/src/service.rs similarity index 95% rename from pagetop/src/service.rs rename to packages/pagetop/src/service.rs index e6bc6373..e28be6e9 100644 --- a/pagetop/src/service.rs +++ b/packages/pagetop/src/service.rs @@ -13,7 +13,7 @@ pub use actix_web_files::Files as ActixFiles; pub use actix_web_static_files::ResourceFiles; #[macro_export] -macro_rules! include_files { +macro_rules! static_files { ( $bundle:ident ) => { $crate::paste! { mod [] { @@ -34,12 +34,11 @@ macro_rules! include_files { } #[macro_export] -macro_rules! include_files_service { +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(|| { - #[allow(unused_mut)] let mut serve_embedded:bool = true; $( if !$root.is_empty() && !$relative.is_empty() { diff --git a/pagetop/src/trace.rs b/packages/pagetop/src/trace.rs similarity index 100% rename from pagetop/src/trace.rs rename to packages/pagetop/src/trace.rs diff --git a/packages/pagetop/src/util.rs b/packages/pagetop/src/util.rs new file mode 100644 index 00000000..ed743491 --- /dev/null +++ b/packages/pagetop/src/util.rs @@ -0,0 +1,306 @@ +//! Useful functions and macros. + +pub mod config; + +mod data; +mod de; +mod error; +mod file; +mod path; +mod source; +mod value; + +use crate::trace; + +use std::io; +use std::path::PathBuf; + +// USEFUL FUNCTIONS ******************************************************************************** + +pub enum TypeInfo { + FullName, + ShortName, + NameFrom(isize), + NameTo(isize), + PartialName(isize, isize), +} + +impl TypeInfo { + pub fn of(&self) -> &'static str { + let type_name = std::any::type_name::(); + match self { + TypeInfo::FullName => type_name, + TypeInfo::ShortName => Self::partial(type_name, -1, None), + TypeInfo::NameFrom(start) => Self::partial(type_name, *start, None), + TypeInfo::NameTo(end) => Self::partial(type_name, 0, Some(*end)), + TypeInfo::PartialName(start, end) => Self::partial(type_name, *start, Some(*end)), + } + } + + fn partial(type_name: &'static str, start: isize, end: Option) -> &'static str { + let maxlen = type_name.len(); + let mut segments = Vec::new(); + let mut segment_start = 0; // Start position of the current segment. + let mut angle_brackets = 0; // Counter for tracking '<' and '>'. + let mut previous_char = '\0'; // Initializes to a null character, no previous character. + + for (idx, c) in type_name.char_indices() { + match c { + ':' if angle_brackets == 0 => { + if previous_char == ':' { + if segment_start < idx - 1 { + segments.push((segment_start, idx - 1)); // Do not include last '::'. + } + segment_start = idx + 1; // Next segment starts after '::'. + } + } + '<' => angle_brackets += 1, + '>' => angle_brackets -= 1, + _ => {} + } + previous_char = c; + } + + // Include the last segment if there's any. + if segment_start < maxlen { + segments.push((segment_start, maxlen)); + } + + // Calculates the start position. + let start_pos = segments + .get(if start >= 0 { + start as usize + } else { + segments.len() - start.unsigned_abs() + }) + .map_or(0, |&(s, _)| s); + + // Calculates the end position. + let end_pos = segments + .get(if let Some(end) = end { + if end >= 0 { + end as usize + } else { + segments.len() - end.unsigned_abs() + } + } else { + segments.len() - 1 + }) + .map_or(maxlen, |&(_, e)| e); + + // Returns the partial string based on the calculated positions. + &type_name[start_pos..end_pos] + } +} + +/// Calculates the absolute directory given a root path and a relative path. +/// +/// # Arguments +/// +/// * `root_path` - A string slice that holds the root path. +/// * `relative_path` - A string slice that holds the relative path. +/// +/// # Returns +/// +/// * `Ok` - If the operation is successful, returns the absolute directory as a `String`. +/// * `Err` - If an I/O error occurs, returns an `io::Error`. +/// +/// # Errors +/// +/// This function will return an error if: +/// - The root path or relative path are invalid. +/// - There is an issue with file system operations, such as reading the directory. +/// +/// # Examples +/// +/// ``` +/// let root = "/home/user"; +/// let relative = "documents"; +/// let abs_dir = absolute_dir(root, relative).unwrap(); +/// println!("{}", abs_dir); +/// ``` +pub fn absolute_dir( + root_path: impl Into, + relative_path: impl Into, +) -> Result { + let root_path = PathBuf::from(root_path.into()); + let full_path = root_path.join(relative_path.into()); + let absolute_dir = full_path.to_string_lossy().into(); + + if !full_path.is_absolute() { + let message = format!("Path \"{absolute_dir}\" is not absolute"); + trace::warn!(message); + return Err(io::Error::new(io::ErrorKind::InvalidInput, message)); + } + + if !full_path.exists() { + let message = format!("Path \"{absolute_dir}\" does not exist"); + trace::warn!(message); + return Err(io::Error::new(io::ErrorKind::NotFound, message)); + } + + if !full_path.is_dir() { + let message = format!("Path \"{absolute_dir}\" is not a directory"); + trace::warn!(message); + return Err(io::Error::new(io::ErrorKind::InvalidInput, message)); + } + + Ok(absolute_dir) +} + +// USEFUL MACROS *********************************************************************************** + +#[macro_export] +/// Macro para construir grupos de pares clave-valor. +/// +/// ```rust#ignore +/// let args = kv![ +/// "userName" => "Roberto", +/// "photoCount" => 3, +/// "userGender" => "male", +/// ]; +/// ``` +macro_rules! kv { + ( $($key:expr => $value:expr),* $(,)? ) => {{ + let mut a = std::collections::HashMap::new(); + $( + a.insert($key.into(), $value.into()); + )* + a + }}; +} + +#[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, +/// 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` 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), + } + }); + }; +} diff --git a/packages/pagetop/src/util/config.rs b/packages/pagetop/src/util/config.rs new file mode 100644 index 00000000..ad628ed3 --- /dev/null +++ b/packages/pagetop/src/util/config.rs @@ -0,0 +1,51 @@ +//! Retrieve settings values from configuration files. + +use crate::concat_string; +use crate::util::data::ConfigData; +use crate::util::file::File; + +use std::sync::LazyLock; + +use std::env; +use std::path::Path; + +/// Original configuration values in `key = value` pairs gathered from configuration files. +pub static CONFIG_DATA: LazyLock = 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 +}); diff --git a/pagetop/src/config/data.rs b/packages/pagetop/src/util/data.rs similarity index 96% rename from pagetop/src/config/data.rs rename to packages/pagetop/src/util/data.rs index 22fe8359..b62803fb 100644 --- a/pagetop/src/config/data.rs +++ b/packages/pagetop/src/util/data.rs @@ -1,7 +1,7 @@ -use crate::config::error::*; -use crate::config::path; -use crate::config::source::Source; -use crate::config::value::Value; +use crate::util::error::*; +use crate::util::path; +use crate::util::source::Source; +use crate::util::value::Value; use serde::de::Deserialize; diff --git a/pagetop/src/config/de.rs b/packages/pagetop/src/util/de.rs similarity index 99% rename from pagetop/src/config/de.rs rename to packages/pagetop/src/util/de.rs index 875219af..55a58037 100644 --- a/pagetop/src/config/de.rs +++ b/packages/pagetop/src/util/de.rs @@ -1,6 +1,6 @@ -use crate::config::data::ConfigData; -use crate::config::error::*; -use crate::config::value::{Table, Value, ValueKind}; +use crate::util::data::ConfigData; +use crate::util::error::*; +use crate::util::value::{Table, Value, ValueKind}; use serde::de; use serde::forward_to_deserialize_any; diff --git a/pagetop/src/config/error.rs b/packages/pagetop/src/util/error.rs similarity index 100% rename from pagetop/src/config/error.rs rename to packages/pagetop/src/util/error.rs diff --git a/pagetop/src/config/file.rs b/packages/pagetop/src/util/file.rs similarity index 95% rename from pagetop/src/config/file.rs rename to packages/pagetop/src/util/file.rs index 00f0c34d..643f4ea1 100644 --- a/pagetop/src/config/file.rs +++ b/packages/pagetop/src/util/file.rs @@ -1,9 +1,9 @@ mod source; mod toml; -use crate::config::error::*; -use crate::config::source::Source; -use crate::config::value::Value; +use crate::util::error::*; +use crate::util::source::Source; +use crate::util::value::Value; use std::collections::HashMap; use std::path::{Path, PathBuf}; diff --git a/pagetop/src/config/file/source.rs b/packages/pagetop/src/util/file/source.rs similarity index 100% rename from pagetop/src/config/file/source.rs rename to packages/pagetop/src/util/file/source.rs diff --git a/pagetop/src/config/file/toml.rs b/packages/pagetop/src/util/file/toml.rs similarity index 96% rename from pagetop/src/config/file/toml.rs rename to packages/pagetop/src/util/file/toml.rs index e8fa06c6..88e8230f 100644 --- a/pagetop/src/config/file/toml.rs +++ b/packages/pagetop/src/util/file/toml.rs @@ -1,4 +1,4 @@ -use crate::config::value::{Value, ValueKind}; +use crate::util::value::{Value, ValueKind}; use toml; diff --git a/pagetop/src/config/path.rs b/packages/pagetop/src/util/path.rs similarity index 98% rename from pagetop/src/config/path.rs rename to packages/pagetop/src/util/path.rs index 72376a95..8b365d6a 100644 --- a/pagetop/src/config/path.rs +++ b/packages/pagetop/src/util/path.rs @@ -1,5 +1,5 @@ -use crate::config::error::*; -use crate::config::value::{Value, ValueKind}; +use crate::util::error::*; +use crate::util::value::{Value, ValueKind}; use std::collections::HashMap; use std::str::FromStr; diff --git a/pagetop/src/config/path/parser.rs b/packages/pagetop/src/util/path/parser.rs similarity index 100% rename from pagetop/src/config/path/parser.rs rename to packages/pagetop/src/util/path/parser.rs diff --git a/pagetop/src/config/source.rs b/packages/pagetop/src/util/source.rs similarity index 95% rename from pagetop/src/config/source.rs rename to packages/pagetop/src/util/source.rs index 5e693b68..5b1ae11d 100644 --- a/pagetop/src/config/source.rs +++ b/packages/pagetop/src/util/source.rs @@ -1,6 +1,6 @@ -use crate::config::error::*; -use crate::config::path; -use crate::config::value::{Value, ValueKind}; +use crate::util::error::*; +use crate::util::path; +use crate::util::value::{Value, ValueKind}; use std::collections::HashMap; use std::fmt::Debug; diff --git a/pagetop/src/config/value.rs b/packages/pagetop/src/util/value.rs similarity index 99% rename from pagetop/src/config/value.rs rename to packages/pagetop/src/util/value.rs index 29d62cfe..cee2dc32 100644 --- a/pagetop/src/config/value.rs +++ b/packages/pagetop/src/util/value.rs @@ -1,4 +1,4 @@ -use crate::config::error::*; +use crate::util::error::*; use serde::de::{Deserialize, Deserializer, Visitor}; diff --git a/pagetop/config/common.toml b/pagetop/config/common.toml deleted file mode 100644 index 900872d6..00000000 --- a/pagetop/config/common.toml +++ /dev/null @@ -1,5 +0,0 @@ -[app] -name = "Samples" - -[log] -tracing = "Debug" diff --git a/pagetop/src/config.rs b/pagetop/src/config.rs deleted file mode 100644 index 07924057..00000000 --- a/pagetop/src/config.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Load configuration settings. -//! -//! These settings are loaded from [TOML](https://toml.io) files as `key = value` pairs and mapped -//! into type-safe structures with predefined values. -//! -//! Following the [Twelve-Factor App](https://12factor.net/config) methodology, `PageTop` separates -//! code from configuration. This approach allows configurations to vary across deployments, such as -//! development, staging, or production, without changing the codebase. -//! -//! -//! # Loading configuration settings -//! -//! If your application requires configuration files, create a `config` directory in the root of -//! your project, at the same level as the *Cargo.toml* file or the application's binary. -//! -//! `PageTop` automatically loads configuration settings by reading the following TOML files in -//! order (all files are optional): -//! -//! 1. **config/common.toml**, for settings shared across all environments. This approach simplifies -//! maintenance by centralizing common configuration values. -//! -//! 2. **config/{rm}.toml**, where `{rm}` corresponds to the environment variable -//! `PAGETOP_RUN_MODE`: -//! -//! * If `PAGETOP_RUN_MODE` is not set, it defaults to `default`, and `PageTop` attempts to load -//! *config/default.toml* if available. -//! -//! * Useful for environment-specific configurations, ensuring that each environment -//! (e.g., development, staging, production) has its own settings without affecting others, -//! such as API keys, URLs, or performance-related adjustments. -//! -//! 3. **config/local.{rm}.toml**, useful for local machine-specific configurations: -//! -//! * This file allows you to add or override settings specific to the environment. For example, -//! `local.devel.toml` for development or `local.production.toml` for production tweaks. -//! -//! * It enables developers to tailor settings for their machines within a given environment and -//! is typically not shared or committed to version control systems. -//! -//! 4. **config/local.toml**, for general local settings across all environments, ideal for quick -//! adjustments or temporary values not tied to any specific environment. -//! -//! The configuration settings are merged in the order listed above, with later files overriding -//! earlier ones if there are conflicts. -//! -//! -//! # Adding configuration settings -//! -//! To give your **module** its own configuration settings, add [*serde*](https://docs.rs/serde) as -//! a dependency in your *Cargo.toml* file with the `derive` feature enabled: -//! -//! ```toml -//! [dependencies] -//! serde = { version = "1.0", features = ["derive"] } -//! ``` -//! -//! Then, use the [`include_config!`](crate::include_config) macro to initialize your settings with -//! type-safe structures and predefined values: -//! -//! ``` -//! use pagetop::prelude::*; -//! use serde::Deserialize; -//! -//! include_config!(SETTINGS: Settings => [ -//! // [myapp] -//! "myapp.name" => "Value Name", -//! "myapp.width" => 900, -//! "myapp.height" => 320, -//! ]); -//! -//! #[derive(Debug, Deserialize)] -//! pub struct Settings { -//! pub myapp: MyApp, -//! } -//! -//! #[derive(Debug, Deserialize)] -//! pub struct MyApp { -//! pub name: String, -//! pub description: Option, -//! pub width: u16, -//! pub height: u16, -//! } -//! ``` -//! -//! This is how global configuration settings are declared (see [`SETTINGS`](crate::global::SETTINGS)). -//! -//! You can add a new `[myapp]` section in the configuration files using the -//! [TOML syntax](https://toml.io/en/v1.0.0#table), just like the `[log]` or `[server]` sections in -//! the global settings (see [`Settings`](crate::global::Settings)). -//! -//! It is recommended to initialize all settings with predefined values or use `Option` for -//! optional settings handled within the code. -//! -//! If configuration settings fail to initialize correctly, the application will panic and stop -//! execution. -//! -//! Configuration settings are always read-only. -//! -//! -//! # Using your new configuration settings -//! -//! Access the settings directly in your code: -//! -//! ``` -//! use pagetop::prelude::*; -//! use crate::config; -//! -//! fn global_settings() { -//! println!("App name: {}", &global::SETTINGS.app.name); -//! println!("App description: {}", &global::SETTINGS.app.description); -//! println!("Value of PAGETOP_RUN_MODE: {}", &global::SETTINGS.app.run_mode); -//! } -//! -//! fn package_settings() { -//! println!("{} - {:?}", &config::SETTINGS.myapp.name, &config::SETTINGS.myapp.description); -//! println!("{}", &config::SETTINGS.myapp.width); -//! } -//! ``` - -mod data; -mod de; -mod error; -mod file; -mod path; -mod source; -mod value; - -use crate::concat_string; -use crate::config::data::ConfigData; -use crate::config::file::File; - -use std::sync::LazyLock; - -use std::env; -use std::path::Path; - -/// Original values read from configuration files in `key = value` pairs. -pub static CONFIG_VALUES: LazyLock = LazyLock::new(|| { - // Identify the configuration directory. - let config_dir = env::var("CARGO_MANIFEST_DIR") - .map(|manifest_dir| { - let manifest_config = Path::new(&manifest_dir).join("config"); - if manifest_config.exists() { - manifest_config.to_string_lossy().to_string() - } else { - "config".to_string() - } - }) - .unwrap_or_else(|_| "config".to_string()); - - // 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 config values. - let mut values = ConfigData::default(); - - // Merge (optional) configuration files and set the execution mode. - values - // First, add the common configuration for all environments. Defaults to 'common.toml'. - .merge(File::with_name(&concat_string!(config_dir, "/common.toml")).required(false)) - .expect("Failed to merge common configuration (common.toml)") - // Add the environment-specific configuration. Defaults to 'default.toml'. - .merge(File::with_name(&concat_string!(config_dir, "/", rm, ".toml")).required(false)) - .expect(&format!("Failed to merge {rm}.toml configuration")) - // Add reserved local configuration for the environment. Defaults to 'local.default.toml'. - .merge(File::with_name(&concat_string!(config_dir, "/local.", rm, ".toml")).required(false)) - .expect("Failed to merge reserved local environment configuration") - // Add common reserved local configuration. Defaults to 'local.toml'. - .merge(File::with_name(&concat_string!(config_dir, "/local.toml")).required(false)) - .expect("Failed to merge general reserved local configuration") - // Save the execution mode. - .set("app.run_mode", rm) - .expect("Failed to set application run mode"); - - values -}); - -#[macro_export] -macro_rules! include_config { - ( $SETTINGS:ident: $Settings:ty => [ $($key:literal => $value:literal),* $(,)? ] ) => { - #[doc = concat!( - "Assigned or predefined values for configuration settings associated to the ", - "[`", stringify!($Settings), "`] type." - )] - pub static $SETTINGS: std::sync::LazyLock<$Settings> = std::sync::LazyLock::new(|| { - let mut settings = $crate::config::CONFIG_VALUES.clone(); - $( - settings.set_default($key, $value).unwrap(); - )* - match settings.try_into() { - Ok(s) => s, - Err(e) => panic!("Error parsing settings: {}", e), - } - }); - }; -} diff --git a/pagetop/src/core/package/welcome.rs b/pagetop/src/core/package/welcome.rs deleted file mode 100644 index 1c3d41c6..00000000 --- a/pagetop/src/core/package/welcome.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::html::{html, Markup, PrepareMarkup, StyleSheet}; -use crate::locale::L10n; -use crate::response::page::{AssetsOp, ErrorPage, Page, ResultPage}; -use crate::{global, service}; - -pub async fn homepage(request: service::HttpRequest) -> ResultPage { - Page::new(request) - .with_title(L10n::l("welcome_page")) - .with_assets(AssetsOp::AddStyleSheet(StyleSheet::inline("styles", r#" - body { - background-color: #f3d060; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - 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; - } - "#))) - .with_body(PrepareMarkup::With(html! { - 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!( - "{}", - &global::SETTINGS.app.name - )).markup()) - } - p class="powered" { - (L10n::l("welcome_powered").with_arg("pagetop", format!( - "{}", - "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()) " ]" } - } - } - })) - .render() -} diff --git a/pagetop/src/core/theme/definition.rs b/pagetop/src/core/theme/definition.rs deleted file mode 100644 index 126e7731..00000000 --- a/pagetop/src/core/theme/definition.rs +++ /dev/null @@ -1,82 +0,0 @@ -use crate::core::package::PackageTrait; -use crate::html::{html, PrepareMarkup}; -use crate::locale::L10n; -use crate::response::page::Page; -use crate::{global, service}; - -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")), - ] - } */ - - fn prepare_page_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()) - } - }) - } - - fn prepare_page_body(&self, page: &mut Page) -> PrepareMarkup { - PrepareMarkup::With(html! { - body id=[page.body_id().get()] class=[page.body_classes().get()] { - (page.body_content().render()) - } - }) - } - - fn error_403(&self, request: service::HttpRequest) -> Page { - Page::new(request) - .with_title(L10n::n("Error FORBIDDEN")) - .with_body(PrepareMarkup::With(html! { - div { - h1 { ("FORBIDDEN ACCESS") } - } - })) - } - - fn error_404(&self, request: service::HttpRequest) -> Page { - Page::new(request) - .with_title(L10n::n("Error RESOURCE NOT FOUND")) - .with_body(PrepareMarkup::With(html! { - div { - h1 { ("RESOURCE NOT FOUND") } - } - })) - } -} diff --git a/pagetop/src/html.rs b/pagetop/src/html.rs deleted file mode 100644 index f9e70cd2..00000000 --- a/pagetop/src/html.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! HTML in code. - -mod maud; -pub use maud::{html, html_private, Markup, PreEscaped, DOCTYPE}; - -mod assets; -pub use assets::favicon::Favicon; -pub use assets::javascript::JavaScript; -pub use assets::stylesheet::{StyleSheet, TargetMedia}; -pub(crate) use assets::Assets; - -mod opt_id; -pub use opt_id::OptionId; - -mod opt_name; -pub use opt_name::OptionName; - -mod opt_string; -pub use opt_string::OptionString; - -mod opt_translated; -pub use opt_translated::OptionTranslated; - -mod opt_classes; -pub use opt_classes::{ClassesOp, OptionClasses}; - -pub mod unit; - -use crate::AutoDefault; - -#[derive(AutoDefault)] -pub enum PrepareMarkup { - #[default] - None, - Escaped(String), - With(Markup), -} - -impl PrepareMarkup { - pub fn render(&self) -> Markup { - match self { - PrepareMarkup::None => html! {}, - PrepareMarkup::Escaped(string) => html! { (PreEscaped(string)) }, - PrepareMarkup::With(markup) => html! { (markup) }, - } - } -} diff --git a/pagetop/src/html/assets.rs b/pagetop/src/html/assets.rs deleted file mode 100644 index 4c8f27ce..00000000 --- a/pagetop/src/html/assets.rs +++ /dev/null @@ -1,53 +0,0 @@ -pub mod favicon; -pub mod javascript; -pub mod stylesheet; - -use crate::html::{html, Markup}; -use crate::{AutoDefault, Weight}; - -pub trait AssetsTrait { - fn name(&self) -> &String; - - fn weight(&self) -> Weight; - - fn prepare(&self) -> Markup; -} - -#[derive(AutoDefault)] -pub(crate) struct Assets(Vec); - -impl Assets { - pub fn new() -> Self { - Assets::(Vec::::new()) - } - - pub fn add(&mut self, asset: T) -> &mut Self { - match self.0.iter().position(|x| x.name() == asset.name()) { - Some(index) => { - if self.0[index].weight() > asset.weight() { - self.0.remove(index); - self.0.push(asset); - } - } - _ => self.0.push(asset), - }; - self - } - - pub fn remove(&mut self, name: &'static str) -> &mut Self { - if let Some(index) = self.0.iter().position(|x| x.name() == name) { - self.0.remove(index); - }; - self - } - - pub fn prepare(&mut self) -> Markup { - let assets = &mut self.0; - assets.sort_by_key(AssetsTrait::weight); - html! { - @for a in assets { - (a.prepare()) - } - } - } -} diff --git a/pagetop/src/html/assets/favicon.rs b/pagetop/src/html/assets/favicon.rs deleted file mode 100644 index 068efcb4..00000000 --- a/pagetop/src/html/assets/favicon.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::html::{html, Markup}; -use crate::AutoDefault; - -#[derive(AutoDefault)] -pub struct Favicon(Vec); - -impl Favicon { - pub fn new() -> Self { - Favicon::default() - } - - // Favicon BUILDER. - - pub fn with_icon(self, image: &str) -> Self { - self.add_icon_item("icon", image, None, None) - } - - pub fn with_icon_for_sizes(self, image: &str, sizes: &str) -> Self { - self.add_icon_item("icon", image, Some(sizes), None) - } - - pub fn with_apple_touch_icon(self, image: &str, sizes: &str) -> Self { - self.add_icon_item("apple-touch-icon", image, Some(sizes), None) - } - - pub fn with_mask_icon(self, image: &str, color: &str) -> Self { - self.add_icon_item("mask-icon", image, None, Some(color)) - } - - pub fn with_manifest(self, file: &str) -> Self { - self.add_icon_item("manifest", file, None, None) - } - - pub fn with_theme_color(mut self, color: &str) -> Self { - self.0.push(html! { - meta name="theme-color" content=(color); - }); - self - } - - pub fn with_ms_tile_color(mut self, color: &str) -> Self { - self.0.push(html! { - meta name="msapplication-TileColor" content=(color); - }); - self - } - - pub fn with_ms_tile_image(mut self, image: &str) -> Self { - self.0.push(html! { - meta name="msapplication-TileImage" content=(image); - }); - self - } - - fn add_icon_item( - mut self, - icon_rel: &str, - icon_source: &str, - icon_sizes: Option<&str>, - icon_color: Option<&str>, - ) -> Self { - let icon_type = match icon_source.rfind('.') { - Some(i) => match icon_source[i..].to_owned().to_lowercase().as_str() { - ".gif" => Some("image/gif"), - ".ico" => Some("image/x-icon"), - ".jpg" => Some("image/jpg"), - ".png" => Some("image/png"), - ".svg" => Some("image/svg+xml"), - _ => None, - }, - _ => None, - }; - self.0.push(html! { - link - rel=(icon_rel) - type=[(icon_type)] - sizes=[(icon_sizes)] - color=[(icon_color)] - href=(icon_source); - }); - self - } - - // Favicon PREPARE. - - pub(crate) fn prepare(&self) -> Markup { - html! { - @for item in &self.0 { - (item) - } - } - } -} diff --git a/pagetop/src/html/assets/javascript.rs b/pagetop/src/html/assets/javascript.rs deleted file mode 100644 index 672ab3e0..00000000 --- a/pagetop/src/html/assets/javascript.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::html::assets::AssetsTrait; -use crate::html::{html, Markup}; -use crate::{concat_string, AutoDefault, Weight}; - -#[derive(AutoDefault)] -enum Source { - #[default] - From(String), - Defer(String), - Async(String), - Inline(String, String), - OnLoad(String, String), -} - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct JavaScript { - source : Source, - prefix : &'static str, - version: &'static str, - weight : Weight, -} - -impl AssetsTrait for JavaScript { - fn name(&self) -> &String { - match &self.source { - Source::From(path) => path, - Source::Defer(path) => path, - Source::Async(path) => path, - Source::Inline(name, _) => name, - Source::OnLoad(name, _) => name, - } - } - - fn weight(&self) -> Weight { - self.weight - } - - fn prepare(&self) -> Markup { - match &self.source { - Source::From(path) => html! { - script src=(concat_string!(path, self.prefix, self.version)) {}; - }, - Source::Defer(path) => html! { - script src=(concat_string!(path, self.prefix, self.version)) defer {}; - }, - Source::Async(path) => html! { - script src=(concat_string!(path, self.prefix, self.version)) async {}; - }, - Source::Inline(_, code) => html! { - script { (code) }; - }, - Source::OnLoad(_, code) => html! { (concat_string!( - "document.addEventListener('DOMContentLoaded',function(){", - code, - "});" - )) }, - } - } -} - -impl JavaScript { - pub fn from(path: impl Into) -> Self { - JavaScript { - source: Source::From(path.into()), - ..Default::default() - } - } - - pub fn defer(path: impl Into) -> Self { - JavaScript { - source: Source::Defer(path.into()), - ..Default::default() - } - } - - pub fn asynchronous(path: impl Into) -> Self { - JavaScript { - source: Source::Async(path.into()), - ..Default::default() - } - } - - pub fn inline(name: impl Into, script: impl Into) -> Self { - JavaScript { - source: Source::Inline(name.into(), script.into()), - ..Default::default() - } - } - - pub fn on_load(name: impl Into, script: impl Into) -> Self { - JavaScript { - source: Source::OnLoad(name.into(), script.into()), - ..Default::default() - } - } - - pub fn with_version(mut self, version: &'static str) -> Self { - (self.prefix, self.version) = if version.is_empty() { - ("", "") - } else { - ("?v=", version) - }; - self - } - - pub fn with_weight(mut self, value: Weight) -> Self { - self.weight = value; - self - } -} diff --git a/pagetop/src/html/assets/stylesheet.rs b/pagetop/src/html/assets/stylesheet.rs deleted file mode 100644 index 11dde4ef..00000000 --- a/pagetop/src/html/assets/stylesheet.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::html::assets::AssetsTrait; -use crate::html::{html, Markup, PreEscaped}; -use crate::{concat_string, AutoDefault, Weight}; - -#[derive(AutoDefault)] -enum Source { - #[default] - From(String), - Inline(String, String), -} - -pub enum TargetMedia { - Default, - Print, - Screen, - Speech, -} - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct StyleSheet { - source : Source, - prefix : &'static str, - version: &'static str, - media : Option<&'static str>, - weight : Weight, -} - -impl AssetsTrait for StyleSheet { - fn name(&self) -> &String { - match &self.source { - Source::From(path) => path, - Source::Inline(name, _) => name, - } - } - - fn weight(&self) -> Weight { - self.weight - } - - fn prepare(&self) -> Markup { - match &self.source { - Source::From(path) => html! { - link - rel="stylesheet" - href=(concat_string!(path, self.prefix, self.version)) - media=[self.media]; - }, - Source::Inline(_, code) => html! { - style { (PreEscaped(code)) }; - }, - } - } -} - -impl StyleSheet { - pub fn from(path: impl Into) -> Self { - StyleSheet { - source: Source::From(path.into()), - ..Default::default() - } - } - - pub fn inline(name: impl Into, styles: impl Into) -> Self { - StyleSheet { - source: Source::Inline(name.into(), styles.into()), - ..Default::default() - } - } - - pub fn with_version(mut self, version: &'static str) -> Self { - (self.prefix, self.version) = if version.is_empty() { - ("", "") - } else { - ("?v=", version) - }; - self - } - - pub fn with_weight(mut self, value: Weight) -> Self { - self.weight = value; - self - } - - #[rustfmt::skip] - pub fn for_media(mut self, media: &TargetMedia) -> Self { - self.media = match media { - TargetMedia::Default => None, - TargetMedia::Print => Some("print"), - TargetMedia::Screen => Some("screen"), - TargetMedia::Speech => Some("speech"), - }; - self - } -} diff --git a/pagetop/src/html/opt_classes.rs b/pagetop/src/html/opt_classes.rs deleted file mode 100644 index 453991cd..00000000 --- a/pagetop/src/html/opt_classes.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! **OptionClasses** implements a *helper* for dynamically adding class names to components. -//! -//! This *helper* differentiates between default classes (generally associated with styles provided -//! by the theme) and user classes (for customizing components based on application styles). -//! -//! Classes can be added using [Add]. Operations to [Remove], [Replace] or [Toggle] a class, as well -//! as [Clear] all classes, are also provided. -//! -//! **OptionClasses** assumes that the order of the classes is irrelevant -//! (), and duplicate classes will not be allowed. - -use crate::{fn_builder, AutoDefault}; - -pub enum ClassesOp { - Add, - Prepend, - Remove, - Replace(String), - Toggle, - Set, -} - -#[derive(AutoDefault)] -pub struct OptionClasses(Vec); - -impl OptionClasses { - pub fn new(classes: impl Into) -> Self { - OptionClasses::default().with_value(ClassesOp::Prepend, classes) - } - - // OptionClasses BUILDER. - - #[fn_builder] - pub fn set_value(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { - let classes: String = classes.into(); - let classes: Vec<&str> = classes.split_ascii_whitespace().collect(); - - if classes.is_empty() { - return self; - } - - match op { - ClassesOp::Add => { - self.add(&classes, self.0.len()); - } - ClassesOp::Prepend => { - self.add(&classes, 0); - } - ClassesOp::Remove => { - for class in classes { - self.0.retain(|c| c.ne(&class.to_string())); - } - } - ClassesOp::Replace(classes_to_replace) => { - let mut pos = self.0.len(); - let replace: Vec<&str> = classes_to_replace.split_ascii_whitespace().collect(); - for class in replace { - if let Some(replace_pos) = self.0.iter().position(|c| c.eq(class)) { - self.0.remove(replace_pos); - if pos > replace_pos { - pos = replace_pos; - } - } - } - self.add(&classes, pos); - } - ClassesOp::Toggle => { - for class in classes { - if !class.is_empty() { - if let Some(pos) = self.0.iter().position(|c| c.eq(class)) { - self.0.remove(pos); - } else { - self.0.push(class.to_string()); - } - } - } - } - ClassesOp::Set => { - self.0.clear(); - self.add(&classes, 0); - } - } - - self - } - - #[inline] - fn add(&mut self, classes: &[&str], mut pos: usize) { - for &class in classes { - if !class.is_empty() && !self.0.iter().any(|c| c == class) { - self.0.insert(pos, class.to_string()); - pos += 1; - } - } - } - - // OptionClasses GETTERS. - - pub fn get(&self) -> Option { - if self.0.is_empty() { - None - } else { - Some(self.0.join(" ")) - } - } - - pub fn contains(&self, class: impl Into) -> bool { - let class: String = class.into(); - self.0.iter().any(|c| c.eq(&class)) - } -} diff --git a/pagetop/src/html/opt_id.rs b/pagetop/src/html/opt_id.rs deleted file mode 100644 index 80e98325..00000000 --- a/pagetop/src/html/opt_id.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{fn_builder, AutoDefault}; - -#[derive(AutoDefault)] -pub struct OptionId(Option); - -impl OptionId { - pub fn new(value: impl Into) -> Self { - OptionId::default().with_value(value) - } - - // OptionId BUILDER. - - #[fn_builder] - pub fn set_value(&mut self, value: impl Into) -> &mut Self { - self.0 = Some(value.into().trim().replace(' ', "_")); - self - } - - // OptionId GETTERS. - - pub fn get(&self) -> Option { - if let Some(value) = &self.0 { - if !value.is_empty() { - return Some(value.to_owned()); - } - } - None - } -} diff --git a/pagetop/src/html/opt_name.rs b/pagetop/src/html/opt_name.rs deleted file mode 100644 index 5ba0c486..00000000 --- a/pagetop/src/html/opt_name.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{fn_builder, AutoDefault}; - -#[derive(AutoDefault)] -pub struct OptionName(Option); - -impl OptionName { - pub fn new(value: impl Into) -> Self { - OptionName::default().with_value(value) - } - - // OptionName BUILDER. - - #[fn_builder] - pub fn set_value(&mut self, value: impl Into) -> &mut Self { - self.0 = Some(value.into().trim().replace(' ', "_")); - self - } - - // OptionName GETTERS. - - pub fn get(&self) -> Option { - if let Some(value) = &self.0 { - if !value.is_empty() { - return Some(value.to_owned()); - } - } - None - } -} diff --git a/pagetop/src/html/opt_string.rs b/pagetop/src/html/opt_string.rs deleted file mode 100644 index 7de22486..00000000 --- a/pagetop/src/html/opt_string.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{fn_builder, AutoDefault}; - -#[derive(AutoDefault)] -pub struct OptionString(Option); - -impl OptionString { - pub fn new(value: impl Into) -> Self { - OptionString::default().with_value(value) - } - - // OptionString BUILDER. - - #[fn_builder] - pub fn set_value(&mut self, value: impl Into) -> &mut Self { - self.0 = Some(value.into().trim().to_owned()); - self - } - - // OptionString GETTERS. - - pub fn get(&self) -> Option { - if let Some(value) = &self.0 { - if !value.is_empty() { - return Some(value.to_owned()); - } - } - None - } -} diff --git a/pagetop/src/html/opt_translated.rs b/pagetop/src/html/opt_translated.rs deleted file mode 100644 index e50a073f..00000000 --- a/pagetop/src/html/opt_translated.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::html::Markup; -use crate::locale::{L10n, LanguageIdentifier}; -use crate::{fn_builder, AutoDefault}; - -#[derive(AutoDefault)] -pub struct OptionTranslated(L10n); - -impl OptionTranslated { - pub fn new(value: L10n) -> Self { - OptionTranslated(value) - } - - // OptionTranslated BUILDER. - - #[fn_builder] - pub fn set_value(&mut self, value: L10n) -> &mut Self { - self.0 = value; - self - } - - // OptionTranslated GETTERS. - - pub fn using(&self, langid: &LanguageIdentifier) -> Option { - self.0.using(langid) - } - - pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup { - self.0.escaped(langid) - } -} diff --git a/pagetop/src/html/unit.rs b/pagetop/src/html/unit.rs deleted file mode 100644 index 5a153c55..00000000 --- a/pagetop/src/html/unit.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::AutoDefault; - -use std::fmt; - -// About pixels: Pixels (px) are relative to the viewing device. For low-dpi devices, 1px is one -// device pixel (dot) of the display. For printers and high resolution screens 1px implies multiple -// device pixels. - -// About em: 2em means 2 times the size of the current font. The em and rem units are practical in -// creating perfectly scalable layout! - -// About viewport: If the browser window size is 50cm wide, 1vw = 0.5cm. - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub enum Value { - #[default] - None, - Auto, - - Cm(isize), // Centimeters. - In(isize), // Inches (1in = 96px = 2.54cm). - Mm(isize), // Millimeters. - Pc(isize), // Picas (1pc = 12pt). - Pt(isize), // Points (1pt = 1/72 of 1in). - Px(isize), // Pixels (1px = 1/96th of 1in). - - RelEm(f32), // Relative to the font-size of the element. - RelPct(f32), // Percentage relative to the parent element. - RelRem(f32), // Relative to font-size of the root element. - RelVh(f32), // Relative to 1% of the height of the viewport. - RelVw(f32), // Relative to 1% of the value of the viewport. -} - -#[rustfmt::skip] -impl fmt::Display for Value { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Value::None => write!(f, ""), - Value::Auto => write!(f, "auto"), - // Absolute value. - Value::Cm(av) => write!(f, "{av}cm"), - Value::In(av) => write!(f, "{av}in"), - Value::Mm(av) => write!(f, "{av}mm"), - Value::Pc(av) => write!(f, "{av}pc"), - Value::Pt(av) => write!(f, "{av}pt"), - Value::Px(av) => write!(f, "{av}px"), - // Relative value. - Value::RelEm(rv) => write!(f, "{rv}em"), - Value::RelPct(rv) => write!(f, "{rv}%"), - Value::RelRem(rv) => write!(f, "{rv}rem"), - Value::RelVh(rv) => write!(f, "{rv}vh"), - Value::RelVw(rv) => write!(f, "{rv}vw"), - } - } -} diff --git a/pagetop/src/locale/en-US/languages.ftl b/pagetop/src/locale/en-US/languages.ftl deleted file mode 100644 index 1e816605..00000000 --- a/pagetop/src/locale/en-US/languages.ftl +++ /dev/null @@ -1,5 +0,0 @@ -english = English -english_british = English (British) -english_united_states = English (United States) -spanish = Spanish -spanish_spain = Spanish (Spain) diff --git a/pagetop/src/locale/en-US/welcome.ftl b/pagetop/src/locale/en-US/welcome.ftl deleted file mode 100644 index d117f462..00000000 --- a/pagetop/src/locale/en-US/welcome.ftl +++ /dev/null @@ -1,20 +0,0 @@ -welcome_title = Hello world! - -welcome_intro = Verifying the installation of { $app }. -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 Rust-based web development framework designed to create modular, extensible, and configurable web solutions. -welcome_pagetop_text3 = For detailed information, please visit the official technical documentation. - -welcome_issues_title = Reporting Issues -welcome_issues_text1 = To report any issues with PageTop, please use GitHub. 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 = Coding is creating diff --git a/pagetop/src/locale/es-ES/languages.ftl b/pagetop/src/locale/es-ES/languages.ftl deleted file mode 100644 index ee74ec26..00000000 --- a/pagetop/src/locale/es-ES/languages.ftl +++ /dev/null @@ -1,5 +0,0 @@ -english = Inglés -english_british = Inglés (Gran Bretaña) -english_united_states = Inglés (Estados Unidos) -spanish = Español -spanish_spain = Español (España) diff --git a/pagetop/src/locale/es-ES/welcome.ftl b/pagetop/src/locale/es-ES/welcome.ftl deleted file mode 100644 index b351bfc9..00000000 --- a/pagetop/src/locale/es-ES/welcome.ftl +++ /dev/null @@ -1,20 +0,0 @@ -welcome_title = ¡Hola mundo! - -welcome_intro = Verificando la instalación de { $app }. -welcome_powered = Una solución web creada con { $pagetop }. - -welcome_page = Página de Bienvenida -welcome_subtitle = ¿Eres usuario de { $app }? -welcome_text1 = Si no sabes por qué se muestra 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 Rust, diseñado para crear soluciones web modulares, extensibles y configurables. -welcome_pagetop_text3 = Para más información visita la documentación técnica oficial. - -welcome_issues_title = Informando Problemas -welcome_issues_text1 = Para comunicar cualquier problema con PageTop utiliza GitHub. 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 = Programar es crear diff --git a/pagetop/src/response/page.rs b/pagetop/src/response/page.rs deleted file mode 100644 index 66ff300c..00000000 --- a/pagetop/src/response/page.rs +++ /dev/null @@ -1,195 +0,0 @@ -mod error; -pub use error::ErrorPage; - -mod context; -pub use context::{AssetsOp, ContextPage /*, ParamError*/}; -/* -pub type FnContextualPath = fn(cx: &Context) -> &str; -*/ - -use crate::fn_builder; -use crate::html::{html, Markup, PrepareMarkup, DOCTYPE}; -use crate::html::{ClassesOp, OptionClasses, OptionId, OptionTranslated}; -use crate::locale::L10n; -use crate::service::HttpRequest; - -pub use actix_web::Result as ResultPage; - -use unic_langid::CharacterDirection; - -#[rustfmt::skip] -pub struct Page { - title : OptionTranslated, - description : OptionTranslated, - metadata : Vec<(&'static str, &'static str)>, - properties : Vec<(&'static str, &'static str)>, - context : ContextPage, - body_id : OptionId, - body_classes: OptionClasses, - body_content: PrepareMarkup, -} - -impl Page { - #[rustfmt::skip] - pub fn new(request: HttpRequest) -> Self { - Page { - title : OptionTranslated::default(), - description : OptionTranslated::default(), - metadata : Vec::default(), - properties : Vec::default(), - context : ContextPage::new(request), - body_id : OptionId::default(), - body_classes: OptionClasses::default(), - body_content: PrepareMarkup::default(), - } - } - - // Page BUILDER. - - #[fn_builder] - pub fn set_title(&mut self, title: L10n) -> &mut Self { - self.title.set_value(title); - self - } - - #[fn_builder] - pub fn set_description(&mut self, description: L10n) -> &mut Self { - self.description.set_value(description); - self - } - - #[fn_builder] - pub fn set_metadata(&mut self, name: &'static str, content: &'static str) -> &mut Self { - self.metadata.push((name, content)); - self - } - - #[fn_builder] - pub fn set_property(&mut self, property: &'static str, content: &'static str) -> &mut Self { - self.metadata.push((property, content)); - self - } - - #[fn_builder] - pub fn set_assets(&mut self, op: AssetsOp) -> &mut Self { - self.context.set_assets(op); - self - } - - #[fn_builder] - pub fn set_body_id(&mut self, id: impl Into) -> &mut Self { - self.body_id.set_value(id); - self - } - - #[fn_builder] - pub fn set_body_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { - self.body_classes.set_value(op, classes); - self - } - - #[fn_builder] - pub fn set_body(&mut self, content: PrepareMarkup) -> &mut Self { - self.body_content = content; - self - } - /* - #[fn_builder] - pub fn set_layout(&mut self, layout: &'static str) -> &mut Self { - self.context.set_assets(AssetsOp::Layout(layout)); - self - } - - #[fn_builder] - pub fn set_regions(&mut self, region: &'static str, op: AnyOp) -> &mut Self { - self.context.set_regions(region, op); - self - } - - pub fn with_component(mut self, component: impl ComponentTrait) -> Self { - self.context - .set_regions("content", AnyOp::Add(AnyComponent::with(component))); - self - } - - pub fn with_component_in( - mut self, - region: &'static str, - component: impl ComponentTrait, - ) -> Self { - self.context - .set_regions(region, AnyOp::Add(AnyComponent::with(component))); - self - } - */ - // Page GETTERS. - - pub fn title(&mut self) -> Option { - self.title.using(self.context.langid()) - } - - pub fn description(&mut self) -> Option { - self.description.using(self.context.langid()) - } - - pub fn metadata(&self) -> &Vec<(&str, &str)> { - &self.metadata - } - - pub fn properties(&self) -> &Vec<(&str, &str)> { - &self.properties - } - - pub fn context(&mut self) -> &mut ContextPage { - &mut self.context - } - - pub fn body_id(&self) -> &OptionId { - &self.body_id - } - - pub fn body_classes(&self) -> &OptionClasses { - &self.body_classes - } - - pub fn body_content(&self) -> &PrepareMarkup { - &self.body_content - } - - // Page RENDER. - - pub fn render(&mut self) -> ResultPage { - // Theme operations before preparing the page body. - //self.context.theme().before_prepare_body(self); - - // Packages actions before preparing the page body. - //action::page::BeforePrepareBody::dispatch(self); - - // Prepare page body. - let body = self.context.theme().prepare_page_body(self); - - // Theme operations after preparing the page body. - //self.context.theme().after_prepare_body(self); - - // Packages actions after preparing the page body. - //action::page::AfterPrepareBody::dispatch(self); - - // Prepare page head. - let head = self.context.theme().prepare_page_head(self); - - // Render the page. - let lang = self.context.langid().language.as_str(); - let dir = match self.context.langid().character_direction() { - CharacterDirection::LTR => "ltr", - CharacterDirection::RTL => "rtl", - CharacterDirection::TTB => "auto", - }; - Ok(html! { - (DOCTYPE) - html lang=(lang) dir=(dir) { - (head.render()) - (body.render()) - } - }) - } -} diff --git a/pagetop/src/response/page/context.rs b/pagetop/src/response/page/context.rs deleted file mode 100644 index 8c4e9078..00000000 --- a/pagetop/src/response/page/context.rs +++ /dev/null @@ -1,208 +0,0 @@ -/* -use crate::base::component::add_base_assets; -use crate::concat_string; -use crate::core::component::AnyOp; */ -use crate::core::theme::all::{theme_by_short_name, DEFAULT_THEME}; -use crate::core::theme::{/*ComponentsInRegions,*/ ThemeRef}; -/* use crate::global::TypeInfo; */ -use crate::html::{html, Markup}; -use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; -use crate::locale::{LanguageIdentifier, DEFAULT_LANGID}; -use crate::service::HttpRequest; -/* -use std::collections::HashMap; -use std::error::Error; -use std::str::FromStr; - -use std::fmt; -*/ - -pub enum AssetsOp { - LangId(&'static LanguageIdentifier), - Theme(&'static str), - //Layout(&'static str), - // Favicon. - SetFavicon(Option), - SetFaviconIfNone(Favicon), - // Stylesheets. - AddStyleSheet(StyleSheet), - RemoveStyleSheet(&'static str), - // JavaScripts. - AddJavaScript(JavaScript), - RemoveJavaScript(&'static str), - // Add assets to properly use base components. - //AddBaseAssets, -} -/* -#[derive(Debug)] -pub enum ParamError { - NotFound, - ParseError(String), -} - -impl fmt::Display for ParamError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ParamError::NotFound => write!(f, "Parameter not found"), - ParamError::ParseError(e) => write!(f, "Parse error: {e}"), - } - } -} - -impl Error for ParamError {} -*/ -#[rustfmt::skip] -pub struct ContextPage { - request : HttpRequest, - langid : &'static LanguageIdentifier, - theme : ThemeRef, /* - layout : &'static str, */ - favicon : Option, - stylesheet: Assets, - javascript: Assets, /* - regions : ComponentsInRegions, - params : HashMap<&'static str, String>, - id_counter: usize, */ -} - -impl ContextPage { - #[rustfmt::skip] - pub(crate) fn new(request: HttpRequest) -> Self { - ContextPage { - request, - langid : &DEFAULT_LANGID, - theme : *DEFAULT_THEME, /* - layout : "default", */ - favicon : None, - stylesheet: Assets::::new(), - javascript: Assets::::new(), /* - regions : ComponentsInRegions::default(), - params : HashMap::<&str, String>::new(), - id_counter: 0,*/ - } - } - - pub fn set_assets(&mut self, op: AssetsOp) -> &mut Self { - match op { - AssetsOp::LangId(langid) => { - self.langid = langid; - } - AssetsOp::Theme(theme_name) => { - self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME); - } /* - AssetsOp::Layout(layout) => { - self.layout = layout; - } */ - // Favicon. - AssetsOp::SetFavicon(favicon) => { - self.favicon = favicon; - } - AssetsOp::SetFaviconIfNone(icon) => { - if self.favicon.is_none() { - self.favicon = Some(icon); - } - } - // Stylesheets. - AssetsOp::AddStyleSheet(css) => { - self.stylesheet.add(css); - } - AssetsOp::RemoveStyleSheet(path) => { - self.stylesheet.remove(path); - } - // JavaScripts. - AssetsOp::AddJavaScript(js) => { - self.javascript.add(js); - } - AssetsOp::RemoveJavaScript(path) => { - self.javascript.remove(path); - } /* - // Add assets to properly use base components. - AssetsOp::AddBaseAssets => { - add_base_assets(self); - } */ - } - self - } - /* - pub fn set_regions(&mut self, region: &'static str, op: AnyOp) -> &mut Self { - self.regions.set_components(region, op); - self - } - - pub fn set_param(&mut self, key: &'static str, value: &T) -> &mut Self { - self.params.insert(key, value.to_string()); - self - } - */ - // Context GETTERS. - - pub fn request(&self) -> &HttpRequest { - &self.request - } - - pub fn langid(&self) -> &LanguageIdentifier { - self.langid - } - - pub fn theme(&self) -> ThemeRef { - self.theme - } - /* - pub fn layout(&self) -> &str { - self.layout - } - - pub fn regions(&self) -> &ComponentsInRegions { - &self.regions - } - - pub fn get_param(&self, key: &'static str) -> Result { - self.params - .get(key) - .ok_or(ParamError::NotFound) - .and_then(|v| T::from_str(v).map_err(|_| ParamError::ParseError(v.clone()))) - } - */ - // Context PREPARE. - - pub(crate) fn prepare_assets(&mut self) -> Markup { - html! { - @if let Some(favicon) = &self.favicon { - (favicon.prepare()) - } - (self.stylesheet.prepare()) - (self.javascript.prepare()) - } - } - /* - pub(crate) fn prepare_region(&mut self, region: impl Into) -> Markup { - self.regions - .all_components(self.theme, region.into().as_str()) - .render(self) - } - - // Context EXTRAS. - - pub fn remove_param(&mut self, key: &'static str) -> bool { - self.params.remove(key).is_some() - } - - pub fn required_id(&mut self, id: Option) -> String { - if let Some(id) = id { - id - } else { - let prefix = TypeInfo::ShortName - .of::() - .trim() - .replace(' ', "_") - .to_lowercase(); - let prefix = if prefix.is_empty() { - "prefix".to_owned() - } else { - prefix - }; - self.id_counter += 1; - concat_string!(prefix, "-", self.id_counter.to_string()) - } - } */ -} diff --git a/pagetop/src/response/page/error.rs b/pagetop/src/response/page/error.rs deleted file mode 100644 index 2dfa93ff..00000000 --- a/pagetop/src/response/page/error.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::core::theme::all::DEFAULT_THEME; -use crate::response::ResponseError; -use crate::service::http::{header::ContentType, StatusCode}; -use crate::service::{HttpRequest, HttpResponse}; - -use std::fmt; - -#[derive(Debug)] -pub enum ErrorPage { - NotModified(HttpRequest), - BadRequest(HttpRequest), - AccessDenied(HttpRequest), - NotFound(HttpRequest), - PreconditionFailed(HttpRequest), - InternalError(HttpRequest), - Timeout(HttpRequest), -} - -impl fmt::Display for ErrorPage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - // Error 304. - ErrorPage::NotModified(_) => write!(f, "Not Modified"), - // Error 400. - ErrorPage::BadRequest(_) => write!(f, "Bad Client Data"), - // Error 403. - ErrorPage::AccessDenied(request) => { - if let Ok(page) = DEFAULT_THEME.error_403(request.clone()).render() { - write!(f, "{}", page.into_string()) - } else { - write!(f, "Access Denied") - } - } - // Error 404. - ErrorPage::NotFound(request) => { - if let Ok(page) = DEFAULT_THEME.error_404(request.clone()).render() { - write!(f, "{}", page.into_string()) - } else { - write!(f, "Not Found") - } - } - // Error 412. - ErrorPage::PreconditionFailed(_) => write!(f, "Precondition Failed"), - // Error 500. - ErrorPage::InternalError(_) => write!(f, "Internal Error"), - // Error 504. - ErrorPage::Timeout(_) => write!(f, "Timeout"), - } - } -} - -impl ResponseError for ErrorPage { - fn error_response(&self) -> HttpResponse { - HttpResponse::build(self.status_code()) - .insert_header(ContentType::html()) - .body(self.to_string()) - } - - #[rustfmt::skip] - fn status_code(&self) -> StatusCode { - match self { - ErrorPage::NotModified(_) => StatusCode::NOT_MODIFIED, - ErrorPage::BadRequest(_) => StatusCode::BAD_REQUEST, - ErrorPage::AccessDenied(_) => StatusCode::FORBIDDEN, - ErrorPage::NotFound(_) => StatusCode::NOT_FOUND, - ErrorPage::PreconditionFailed(_) => StatusCode::PRECONDITION_FAILED, - ErrorPage::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, - ErrorPage::Timeout(_) => StatusCode::GATEWAY_TIMEOUT, - } - } -} diff --git a/pagetop/src/util.rs b/pagetop/src/util.rs deleted file mode 100644 index a34ee599..00000000 --- a/pagetop/src/util.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! Useful functions and macros. - -use crate::trace; - -use std::io; -use std::path::PathBuf; - -// USEFUL FUNCTIONS ******************************************************************************** - -pub enum TypeInfo { - FullName, - ShortName, - NameFrom(isize), - NameTo(isize), - PartialName(isize, isize), -} - -impl TypeInfo { - pub fn of(&self) -> &'static str { - let type_name = std::any::type_name::(); - match self { - TypeInfo::FullName => type_name, - TypeInfo::ShortName => Self::partial(type_name, -1, None), - TypeInfo::NameFrom(start) => Self::partial(type_name, *start, None), - TypeInfo::NameTo(end) => Self::partial(type_name, 0, Some(*end)), - TypeInfo::PartialName(start, end) => Self::partial(type_name, *start, Some(*end)), - } - } - - fn partial(type_name: &'static str, start: isize, end: Option) -> &'static str { - let maxlen = type_name.len(); - let mut segments = Vec::new(); - let mut segment_start = 0; // Start position of the current segment. - let mut angle_brackets = 0; // Counter for tracking '<' and '>'. - let mut previous_char = '\0'; // Initializes to a null character, no previous character. - - for (idx, c) in type_name.char_indices() { - match c { - ':' if angle_brackets == 0 => { - if previous_char == ':' { - if segment_start < idx - 1 { - segments.push((segment_start, idx - 1)); // Do not include last '::'. - } - segment_start = idx + 1; // Next segment starts after '::'. - } - } - '<' => angle_brackets += 1, - '>' => angle_brackets -= 1, - _ => {} - } - previous_char = c; - } - - // Include the last segment if there's any. - if segment_start < maxlen { - segments.push((segment_start, maxlen)); - } - - // Calculates the start position. - let start_pos = segments - .get(if start >= 0 { - start as usize - } else { - segments.len() - start.unsigned_abs() - }) - .map_or(0, |&(s, _)| s); - - // Calculates the end position. - let end_pos = segments - .get(if let Some(end) = end { - if end >= 0 { - end as usize - } else { - segments.len() - end.unsigned_abs() - } - } else { - segments.len() - 1 - }) - .map_or(maxlen, |&(_, e)| e); - - // Returns the partial string based on the calculated positions. - &type_name[start_pos..end_pos] - } -} - -/// Calculates the absolute directory given a root path and a relative path. -/// -/// # Arguments -/// -/// * `root_path` - A string slice that holds the root path. -/// * `relative_path` - A string slice that holds the relative path. -/// -/// # Returns -/// -/// * `Ok` - If the operation is successful, returns the absolute directory as a `String`. -/// * `Err` - If an I/O error occurs, returns an `io::Error`. -/// -/// # Errors -/// -/// This function will return an error if: -/// - The root path or relative path are invalid. -/// - There is an issue with file system operations, such as reading the directory. -/// -/// # Examples -/// -/// ``` -/// let root = "/home/user"; -/// let relative = "documents"; -/// let abs_dir = absolute_dir(root, relative).unwrap(); -/// println!("{}", abs_dir); -/// ``` -pub fn absolute_dir( - root_path: impl Into, - relative_path: impl Into, -) -> Result { - let root_path = PathBuf::from(root_path.into()); - let full_path = root_path.join(relative_path.into()); - let absolute_dir = full_path.to_string_lossy().into(); - - if !full_path.is_absolute() { - let message = format!("Path \"{absolute_dir}\" is not absolute"); - trace::warn!(message); - return Err(io::Error::new(io::ErrorKind::InvalidInput, message)); - } - - if !full_path.exists() { - let message = format!("Path \"{absolute_dir}\" does not exist"); - trace::warn!(message); - return Err(io::Error::new(io::ErrorKind::NotFound, message)); - } - - if !full_path.is_dir() { - let message = format!("Path \"{absolute_dir}\" is not a directory"); - trace::warn!(message); - return Err(io::Error::new(io::ErrorKind::InvalidInput, message)); - } - - Ok(absolute_dir) -} - -// USEFUL MACROS *********************************************************************************** - -#[macro_export] -/// Macro para construir grupos de pares clave-valor. -/// -/// ```rust#ignore -/// let args = kv![ -/// "userName" => "Roberto", -/// "photoCount" => 3, -/// "userGender" => "male", -/// ]; -/// ``` -macro_rules! kv { - ( $($key:expr => $value:expr),* $(,)? ) => {{ - let mut a = std::collections::HashMap::new(); - $( - a.insert($key.into(), $value.into()); - )* - a - }}; -} diff --git a/static/favicon.ico b/static/favicon.ico deleted file mode 100644 index 95e1affa..00000000 Binary files a/static/favicon.ico and /dev/null differ