Skip to content

Commit

Permalink
Add timedelta() template filter and timestamp global
Browse files Browse the repository at this point in the history
Introduce a "timestamp" template global variable with the date/time at
which the "!render" command was issued, and a "timedelta()" filter that
may be used to get new timestamps calculated by adding a time duration
(in seconds, minutes, hours, etc.) to an existing one.

This allows calculating texts like "from September 20 up to October 7"
in templates.
  • Loading branch information
aperezdc committed Oct 9, 2024
1 parent 14e2399 commit 9a2638a
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 3 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ async-process = "2.2"
minijinja = { version = "2.0.1", features = ["builtins", "debug", "deserialization", "json", "loader", "std_collections"] }
minijinja-contrib = { version = "2.0.1", features = ["datetime", "rand"] }
minijinja-autoreload = "2.2.0"
time = "0.3.36"

[features]
default = [
Expand Down
25 changes: 22 additions & 3 deletions doc/example_config/template.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "#{{ now() | dateformat(format='[week_number]') }}: This Week in X"
title: "#{{ timestamp | dateformat(format='[week_number]') }}: This Week in X"
author: {{ editor }}
date: {{ now() | datetimeformat(format="iso") }}
date: {{ timestamp | datetimeformat(format="iso") }}
tags: {{ projects }}
categories: ["weekly-update"]
draft: false
Expand All @@ -22,6 +22,22 @@ draft: false
# used for processing the template. This is useful for writing custom
# templates.
#
# - On top of the MiniJinja built-ins (including the minijinja-contrib ones),
# the following globals are available during template processing:
#
# editor: display name of the editor that issued the !render command.
# timestamp: date/time of the !render command (see more below).
# sections: sections containing projects and news items.
# projects: names of the projects that have news items.
# config: contents of the TOML configuration file.
#
# - The "timestamp" global contains the date and time at which the !render
# command was issued. The timedelta() filter can be used to derive new
# date/time values by adding and subtracting time periods, e.g.:
# "now() | timedelta(weeks=-1)" obtains the date/time for one week
# ago. Accepted parameters are "seconds", "minutes", "hours", "days",
# "weeks", "months", and "years".
#
# - Macros can be used to avoid repeating template fragments. See below
# for an example macro to handle both section and project news.
#
Expand Down Expand Up @@ -50,7 +66,10 @@ draft: false
{%- endfor %} {#- news_items #}
{%- endmacro %}

Update on what happened across the X project in the week from {{timespan}}.
Update on what happened across the X project in the week from {{
timestamp | timedelta(weeks = -1) | dateformat(format="[month repr:long] [day padding:none]")
}} to {{
timestamp | dateformat(format="[month repr:long] [day padding:none]") }}.

{%- for key, entry in sections | dictsort %}

Expand Down
35 changes: 35 additions & 0 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,39 @@ pub struct RenderResult {
pub videos: Vec<(String, OwnedMxcUri)>,
}

fn template_filter_timedelta(
timestamp: minijinja::value::ViaDeserialize<time::OffsetDateTime>,
options: minijinja::value::Kwargs,
) -> Result<minijinja::Value, minijinja::Error> {
let mut duration = time::Duration::seconds(0);
if let Some(seconds) = options.get::<Option<f64>>("seconds")? {
duration = duration.saturating_add(time::Duration::seconds_f64(seconds));
}
if let Some(minutes) = options.get::<Option<i64>>("minutes")? {
duration = duration.saturating_add(time::Duration::minutes(minutes));
}
if let Some(hours) = options.get::<Option<i64>>("hours")? {
duration = duration.saturating_add(time::Duration::hours(hours));
}
if let Some(days) = options.get::<Option<i64>>("days")? {
duration = duration.saturating_add(time::Duration::days(days));
}
if let Some(weeks) = options.get::<Option<i64>>("weeks")? {
duration = duration.saturating_add(time::Duration::days(weeks * 7));
}
if let Some(months) = options.get::<Option<i64>>("months")? {
duration = duration.saturating_add(time::Duration::days(months * 30));
}
if let Some(years) = options.get::<Option<i64>>("years")? {
duration = duration.saturating_add(time::Duration::days(years * 365));
}
options.assert_all_used()?;

Ok(minijinja::Value::from_serialize(
timestamp.saturating_add(duration),
))
}

static RELOADER: LazyLock<minijinja_autoreload::AutoReloader> = LazyLock::new(|| {
minijinja_autoreload::AutoReloader::new(|notifier| {
use std::io::Read;
Expand All @@ -58,6 +91,7 @@ static RELOADER: LazyLock<minijinja_autoreload::AutoReloader> = LazyLock::new(||

let mut env = minijinja::Environment::new();
minijinja_contrib::add_to_environment(&mut env);
env.add_filter("timedelta", template_filter_timedelta);
env.add_template_owned("template", text)?;

notifier.watch_path(path, false);
Expand Down Expand Up @@ -247,6 +281,7 @@ pub fn render(

let env = &*RELOADER.acquire_env()?;
let rendered = env.get_template("template")?.render(minijinja::context! {
timestamp => time::OffsetDateTime::now_utc(),
sections => render_sections,
projects => project_names,
config => config,
Expand Down

0 comments on commit 9a2638a

Please sign in to comment.