commit 017324a1e98ac2da7812e4fcc06fe67d424e4ffb from: msi date: Thu Nov 13 17:07:03 2025 UTC Implement minijinja template commit - b863a55d8a947cfcc6296d34edae4d1365a7725f commit + 017324a1e98ac2da7812e4fcc06fe67d424e4ffb blob - 6890378b5086a50a0ce0de583373098f9b1c4910 blob + 0c41cb9f7eff65760953f32f5bb282e8150efa89 --- web/template/Cargo.toml +++ web/template/Cargo.toml @@ -9,6 +9,7 @@ edition = "2024" [dependencies] anyhow = "=1.0.100" axum = "=0.8.6" +minijinja = "=2.12.0" serde = { version = "=1.0.228", features = ["derive"] } tokio = { version = "=1.48.0", features = ["macros", "rt-multi-thread", "signal"] } tower-http = { version = "=0.6.6", features = ["timeout", "trace", "fs"] } blob - d686877bd50f83c76a929f05bec5389e8c050dc9 blob + 20a1974ddf614cffd2d2a597cde1e84df5850396 --- web/template/src/main.rs +++ web/template/src/main.rs @@ -14,30 +14,30 @@ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // -use std::time::Duration; +use std::sync::Arc; -use axum::{Router, response::Html, routing::get}; +use minijinja::Environment; use tokio::net::TcpListener; -use tower_http::{ - services::ServeDir, timeout::TimeoutLayer, trace::TraceLayer, -}; use tracing::info; mod helpers; +mod router; +mod state; #[tokio::main] async fn main() -> anyhow::Result<()> { helpers::init_tracing(); - let app = Router::new() - .route("/", get(handler)) - // TODO(msi): from config folder asssets - .nest_service("/assets", ServeDir::new("assets")) - .layer(( - TraceLayer::new_for_http(), - TimeoutLayer::new(Duration::from_secs(10)), // TODO(msi): from config - )); + let mut env = Environment::new(); + env.add_template("layout", include_str!("../templates/layout.jinja"))?; + env.add_template("home", include_str!("../templates/home.jinja"))?; + env.add_template("content", include_str!("../templates/content.jinja"))?; + env.add_template("about", include_str!("../templates/about.jinja"))?; + let app_state = Arc::new(state::AppState { env }); + + let app = router::route(app_state); + // TODO(msi): from config let listener = TcpListener::bind("0.0.0.0:3000").await?; info!("listening on http://{}", listener.local_addr().unwrap()); @@ -47,18 +47,3 @@ async fn main() -> anyhow::Result<()> { Ok(()) } - -const INDEX: &'static str = r#" - - - - -

Hello, World {{project-name}} =]

-

Template form https://ijanc.org

- - -"#; - -async fn handler() -> Html<&'static str> { - Html(INDEX) -} blob - /dev/null blob + d1c8ba234bce5e312437dc11de6a28a30a401722 (mode 644) --- /dev/null +++ web/template/src/router.rs @@ -0,0 +1,86 @@ +// +// Copyright (c) 2025 murilo ijanc' +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +use std::{sync::Arc, time::Duration}; + +use axum::{ + Router, extract::State, http::StatusCode, response::Html, routing::get, +}; +use minijinja::context; +use tower_http::{ + services::ServeDir, timeout::TimeoutLayer, trace::TraceLayer, +}; + +use crate::state::AppState; + +pub(crate) fn route(app_state: Arc) -> Router { + Router::new() + .route("/", get(handler_home)) + .route("/content", get(handler_content)) + .route("/about", get(handler_about)) + // TODO(msi): from config folder asssets + .nest_service("/assets", ServeDir::new("assets")) + .layer(( + TraceLayer::new_for_http(), + // TODO(msi): from config + TimeoutLayer::new(Duration::from_secs(10)), + )) + .with_state(app_state) +} + +async fn handler_home( + State(state): State>, +) -> Result, StatusCode> { + let template = state.env.get_template("home").unwrap(); + + let rendered = template + .render(context! { + title => "Home", + welcome_text => "Hello World!", + }) + .unwrap(); + + Ok(Html(rendered)) +} + +async fn handler_content( + State(state): State>, +) -> Result, StatusCode> { + let template = state.env.get_template("content").unwrap(); + + let some_example_entries = vec!["Data 1", "Data 2", "Data 3"]; + + let rendered = template + .render(context! { + title => "Content", + entries => some_example_entries, + }) + .unwrap(); + + Ok(Html(rendered)) +} + +async fn handler_about( + State(state): State>, +) -> Result, StatusCode> { + let template = state.env.get_template("about").unwrap(); + + let rendered = template.render(context!{ + title => "About", + about_text => "Simple demonstration layout for an axum project with minijinja as templating engine.", + }).unwrap(); + + Ok(Html(rendered)) +} blob - /dev/null blob + d47938337992e352bc0b74cff22e47d95d2faee8 (mode 644) --- /dev/null +++ web/template/src/state.rs @@ -0,0 +1,21 @@ +// +// Copyright (c) 2025 murilo ijanc' +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +use minijinja::Environment; + +pub(crate) struct AppState { + pub(crate) env: Environment<'static>, +} blob - /dev/null blob + ba8c97e3ce2e51c04e27ed51b5ba38ce7c7cf63e (mode 644) --- /dev/null +++ web/template/templates/about.jinja @@ -0,0 +1,6 @@ +{% extends "layout" %} +{% block title %}{{ super() }} | {{ title }} {% endblock %} +{% block body %} +

{{ title }}

+

{{ about_text }}

+{% endblock %} blob - /dev/null blob + b3fbfa6c79324b0b5505364120fa210f5261ad26 (mode 644) --- /dev/null +++ web/template/templates/content.jinja @@ -0,0 +1,10 @@ +{% extends "layout" %} +{% block title %}{{ super() }} | {{ title }} {% endblock %} +{% block body %} +

{{ title }}

+{% for data_entry in entries %} +
    +
  • {{ data_entry }}
  • +
+{% endfor %} +{% endblock %} blob - /dev/null blob + 2d231db34bb54c6d2d78c71cf88c9bd7b371cb24 (mode 644) --- /dev/null +++ web/template/templates/home.jinja @@ -0,0 +1,6 @@ +{% extends "layout" %} +{% block title %}{{ super() }} | {{ title }} {% endblock %} +{% block body %} +

{{ title }}

+

{{ welcome_text }}

+{% endblock %} blob - /dev/null blob + 02232bcc6116173cbbcaab69161cdd4ea05e4b02 (mode 644) --- /dev/null +++ web/template/templates/layout.jinja @@ -0,0 +1,17 @@ + + + + {% block title %}Website Name{% endblock %} + + +

Hello, World web =]

+

Template form https://ijanc.org

+ {% block body %}{% endblock %} + +