Skip to content

Replace tera filters with macros #1542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/blogs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ pub struct Blog {
maintained_by: String,
index_html: String,
#[serde(serialize_with = "add_postfix_slash")]
prefix: PathBuf,
posts: Vec<Post>,
path: PathBuf,
pages: Vec<Post>,
}

impl Blog {
fn load(prefix: PathBuf, dir: &Path) -> eyre::Result<Self> {
fn load(path: PathBuf, dir: &Path) -> eyre::Result<Self> {
let manifest_content = std::fs::read_to_string(dir.join(MANIFEST_FILE))?
.strip_prefix("+++\n")
.unwrap()
Expand Down Expand Up @@ -94,8 +94,8 @@ impl Blog {
maintained_by: manifest.maintained_by,
index_html: manifest.index_html,
link_text: manifest.link_text,
prefix,
posts,
path,
pages: posts,
})
}

Expand All @@ -111,12 +111,12 @@ impl Blog {
&self.index_title
}

pub(crate) fn prefix(&self) -> &Path {
&self.prefix
pub(crate) fn path(&self) -> &Path {
&self.path
}

pub(crate) fn posts(&self) -> &[Post] {
&self.posts
&self.pages
}
}

Expand All @@ -139,10 +139,10 @@ fn load_recursive(base: &Path, current: &Path, blogs: &mut Vec<Blog>) -> eyre::R
let file_name = path.file_name().and_then(|n| n.to_str());
if let (Some(file_name), Some(parent)) = (file_name, path.parent()) {
if file_name == MANIFEST_FILE {
let prefix = parent
let path = parent
.strip_prefix(base)
.map_or_else(|_| PathBuf::new(), Path::to_path_buf);
blogs.push(Blog::load(prefix, parent)?);
blogs.push(Blog::load(path, parent)?);
}
}
}
Expand Down
68 changes: 13 additions & 55 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use rayon::prelude::*;
use sass_rs::{Options, compile_file};
use serde::Serialize;
use serde_json::{Value, json};
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
Expand All @@ -33,53 +32,12 @@ struct ReleasePost {
url: String,
}

fn month_name(month_num: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
let month_num = month_num
.as_u64()
.expect("month_num should be an unsigned integer");
let name = match month_num {
1 => "Jan.",
2 => "Feb.",
3 => "Mar.",
4 => "Apr.",
5 => "May",
6 => "June",
7 => "July",
8 => "Aug.",
9 => "Sept.",
10 => "Oct.",
11 => "Nov.",
12 => "Dec.",
_ => panic!("invalid month! ({month_num})"),
};
Ok(name.into())
}

// Tera and Handlebars escape HTML differently by default.
// Tera: &<>"'/
// Handlebars: &<>"'`=
// To make the transition testable, this function escapes just like Handlebars.
fn escape_hbs(input: &Value, _args: &HashMap<String, Value>) -> tera::Result<Value> {
let input = input.as_str().expect("input should be a string");
Ok(input
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#x27;")
.replace("`", "&#x60;")
.replace("=", "&#x3D;")
.into())
}

impl Generator {
fn new(
out_directory: impl AsRef<Path>,
posts_directory: impl AsRef<Path>,
) -> eyre::Result<Self> {
let mut tera = Tera::new("templates/*")?;
tera.register_filter("month_name", month_name);
tera.register_filter("escape_hbs", escape_hbs);
tera.autoescape_on(vec![]); // disable auto-escape for .html templates
Ok(Generator {
tera,
Expand Down Expand Up @@ -148,7 +106,7 @@ impl Generator {
}

fn render_blog(&self, blog: &Blog) -> eyre::Result<()> {
std::fs::create_dir_all(self.out_directory.join(blog.prefix()))?;
std::fs::create_dir_all(self.out_directory.join(blog.path()))?;

let path = self.render_index(blog)?;

Expand Down Expand Up @@ -177,24 +135,24 @@ impl Generator {
.map(|other_blog| {
json!({
"link_text": other_blog.link_text(),
"url": other_blog.prefix().join("index.html"),
"url": other_blog.path().join("index.html"),
})
})
.collect();

let data = json!({
"title": blog.index_title(),
"blog": blog,
"section": blog,
"other_blogs": other_blogs,
});
let path = blog.prefix().join("index.html");
let path = blog.path().join("index.html");
self.render_template(&path, "index.html", data)?;
Ok(path)
}

fn render_post(&self, blog: &Blog, post: &Post) -> eyre::Result<PathBuf> {
let path = blog
.prefix()
.path()
.join(format!("{:04}", &post.year))
.join(format!("{:02}", &post.month))
.join(format!("{:02}", &post.day));
Expand All @@ -206,8 +164,8 @@ impl Generator {

let data = json!({
"title": format!("{} | {}", post.title, blog.title()),
"blog": blog,
"post": post,
"section": blog,
"page": post,
});

let path = path.join(filename);
Expand All @@ -218,12 +176,12 @@ impl Generator {
fn render_feed(&self, blog: &Blog) -> eyre::Result<()> {
let posts: Vec<_> = blog.posts().iter().take(10).collect();
let data = json!({
"blog": blog,
"posts": posts,
"section": blog,
"pages": posts,
"feed_updated": chrono::Utc::now().with_nanosecond(0).unwrap().to_rfc3339(),
});

self.render_template(blog.prefix().join("feed.xml"), "feed.xml", data)?;
self.render_template(blog.path().join("feed.xml"), "feed.xml", data)?;
Ok(())
}

Expand All @@ -235,8 +193,8 @@ impl Generator {
.map(|post| ReleasePost {
title: post.title.clone(),
url: blog
.prefix()
.join(post.url.clone())
.path()
.join(post.path.clone())
.to_string_lossy()
.to_string(),
})
Expand All @@ -246,7 +204,7 @@ impl Generator {
feed_updated: chrono::Utc::now().with_nanosecond(0).unwrap().to_rfc3339(),
};
fs::write(
self.out_directory.join(blog.prefix()).join("releases.json"),
self.out_directory.join(blog.path()).join("releases.json"),
serde_json::to_string(&data)?,
)?;
Ok(())
Expand Down
4 changes: 2 additions & 2 deletions src/posts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct Post {
pub(crate) month: u8,
pub(crate) day: u8,
pub(crate) contents: String,
pub(crate) url: String,
pub(crate) path: String,
pub(crate) published: String,
pub(crate) updated: String,
pub(crate) release: bool,
Expand Down Expand Up @@ -122,7 +122,7 @@ impl Post {
month,
day,
contents,
url,
path: url,
published,
updated,
release,
Expand Down
31 changes: 16 additions & 15 deletions templates/feed.xml
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
{% import "macros.html" as macros %}
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<generator uri="https://blog.rust-lang.org/{{blog.prefix}}" version="0.1.0">{{blog.title}}</generator>
<link href="https://blog.rust-lang.org/{{blog.prefix}}feed.xml" rel="self" type="application/atom+xml" />
<link href="https://blog.rust-lang.org/{{blog.prefix}}" rel="alternate" type="text/html" />
<id>https://blog.rust-lang.org/{{blog.prefix}}</id>
<title>{{blog.title}}</title>
<subtitle>{{blog.description}}</subtitle>
<generator uri="https://blog.rust-lang.org/{{ section.path }}" version="0.1.0">{{ section.title }}</generator>
<link href="https://blog.rust-lang.org/{{ section.path }}feed.xml" rel="self" type="application/atom+xml" />
<link href="https://blog.rust-lang.org/{{ section.path }}" rel="alternate" type="text/html" />
<id>https://blog.rust-lang.org/{{ section.path }}</id>
<title>{{ section.title }}</title>
<subtitle>{{ section.description }}</subtitle>
<author>
<name>Maintained by {{blog.maintained_by}}.</name>
<name>Maintained by {{ section.maintained_by }}.</name>
<uri>https://github.com/rust-lang/blog.rust-lang.org/</uri>
</author>
<updated>{{feed_updated}}</updated>

{% for post in posts %}
{% for page in pages %}
<entry>
<title>{{post.title | escape_hbs}}</title>
<link rel="alternate" href="https://blog.rust-lang.org/{{blog.prefix}}{{post.url | escape_hbs}}" type="text/html" title="{{post.title | escape_hbs}}" />
<published>{{post.published | escape_hbs}}</published>
<updated>{{post.updated | escape_hbs}}</updated>
<id>https://blog.rust-lang.org/{{blog.prefix}}{{post.url | escape_hbs}}</id>
<content type="html" xml:base="https://blog.rust-lang.org/{{blog.prefix}}{{post.url | escape_hbs}}">{{post.contents | escape_hbs}}</content>
<title>{{ macros::escape_hbs(input=page.title) }}</title>
<link rel="alternate" href="https://blog.rust-lang.org/{{ section.path }}{{ macros::escape_hbs(input=page.path) }}" type="text/html" title="{{ macros::escape_hbs(input=page.title) }}" />
<published>{{ macros::escape_hbs(input=page.published) }}</published>
<updated>{{ macros::escape_hbs(input=page.updated) }}</updated>
<id>https://blog.rust-lang.org/{{ section.path }}{{ macros::escape_hbs(input=page.path) }}</id>
<content type="html" xml:base="https://blog.rust-lang.org/{{ section.path }}{{ macros::escape_hbs(input=page.path) }}">{{ macros::escape_hbs(input=page.contents) }}</content>

<author>
<name>{{post.author | escape_hbs}}</name>
<name>{{ macros::escape_hbs(input=page.author) }}</name>
</author>
</entry>
{%- endfor %}
Expand Down
12 changes: 6 additions & 6 deletions templates/headers.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{% macro headers(title, blog) -%}
{% macro headers(title, section) -%}
<!-- Twitter card -->
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@rustlang">
<meta name="twitter:creator" content="@rustlang">
<meta name="twitter:title" content="{{title | escape_hbs}}">
<meta name="twitter:description" content="{{blog.description}}">
<meta name="twitter:title" content="{{ macros::escape_hbs(input=title) }}">
<meta name="twitter:description" content="{{ section.description }}">
<meta name="twitter:image" content="https://www.rust-lang.org/static/images/rust-social.jpg">

<!-- Facebook OpenGraph -->
<meta property="og:title" content="{{title | escape_hbs}}" />
<meta property="og:description" content="{{blog.description}}">
<meta property="og:title" content="{{ macros::escape_hbs(input=title) }}" />
<meta property="og:description" content="{{ section.description }}">
<meta property="og:image" content="https://www.rust-lang.org/static/images/rust-social-wide.jpg" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
Expand All @@ -36,7 +36,7 @@
<meta name="theme-color" content="#ffffff">

<!-- atom -->
<link type="application/atom+xml" rel="alternate" href="https://blog.rust-lang.org/{{blog.prefix}}feed.xml" title="{{blog.title}}" />
<link type="application/atom+xml" rel="alternate" href="https://blog.rust-lang.org/{{ section.path }}feed.xml" title="{{ section.title }}" />

<!-- theme switcher -->
<script src="/scripts/theme-switch.js"></script>
Expand Down
15 changes: 8 additions & 7 deletions templates/index.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
{% import "macros.html" as macros %}
{% extends "layout.html" %}
{% block page %}
<header class="mt3 mt0-ns mb4-ns">
<div class="container flex flex-column flex-row-l justify-between-l">
<div class="mw6-l">
<p>{{blog.index_html}}</p>
<p>{{ section.index_html }}</p>
</div>
</div>
<div class="container flex flex-column flex-row-l justify-between-l">
<div class="mw8-l">
<p>
<b>See also:</b>
{%- for other in other_blogs %}
<a href="/{{other.url}}">{{other.link_text | escape_hbs}}</a>
<a href="/{{other.url}}">{{ macros::escape_hbs(input=other.link_text) }}</a>
{%- endfor %}
</p>
</div>
Expand All @@ -22,14 +23,14 @@
<div class="w-100 mw-none ph3 mw8-m mw9-l center f3">

<table class="post-list collapse w-100 f2-l f2-m f3-s">
{%- for post in blog.posts %}
{% if post.show_year %}<tr>
{%- for page in section.pages %}
{% if page.show_year %}<tr>
<td class="bn"></td>
<td class="bn"><h3 class="f0-l f1-m f2-s mt4 mb0">Posts in {{post.year}}</h3></td>
<td class="bn"><h3 class="f0-l f1-m f2-s mt4 mb0">Posts in {{ page.year }}</h3></td>
</tr>{% endif %}
<tr>
<td class="tr o-60 pr4 pr5-l bn">{{post.month | month_name}}&nbsp;{{post.day}}</td>
<td class="bn"><a href="{{post.url}}">{{post.title | escape_hbs}}</a></td>
<td class="tr o-60 pr4 pr5-l bn">{{ macros::month_name(num=page.month) }}&nbsp;{{ page.day }}</td>
<td class="bn"><a href="{{ page.path }}">{{ macros::escape_hbs(input=page.title) }}</a></td>
</tr>
{%- endfor %}
</table>
Expand Down
7 changes: 4 additions & 3 deletions templates/layout.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
{% import "macros.html" as macros %}
{% import "headers.html" as headers %}
{% import "nav.html" as nav %}
{% import "footer.html" as footer %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ title | escape_hbs }}</title>
<title>{{ macros::escape_hbs(input=title) }}</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="description" content="Empowering everyone to build reliable and efficient software.">
{{ headers::headers(title=title, blog=blog) | indent(prefix=" ", blank=true) }}
{{ headers::headers(title=title, section=section) | indent(prefix=" ", blank=true) }}
</head>
<body>
{{ nav::nav(blog=blog) | indent(prefix=" ", blank=true) }}
{{ nav::nav(section=section) | indent(prefix=" ", blank=true) }}
{%- block page %}{% endblock page %}
{{ footer::footer() | indent(prefix=" ", blank=true) }}
</body>
Expand Down
35 changes: 35 additions & 0 deletions templates/macros.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{% macro month_name(num) %}
{%- if num == 1 %}Jan.
{%- elif num == 2 %}Feb.
{%- elif num == 3 %}Mar.
{%- elif num == 4 %}Apr.
{%- elif num == 5 %}May
{%- elif num == 6 %}June
{%- elif num == 7 %}July
{%- elif num == 8 %}Aug.
{%- elif num == 9 %}Sept.
{%- elif num == 10 %}Oct.
{%- elif num == 11 %}Nov.
{%- elif num == 12 %}Dec.
{%- else %}{{ throw(message="invalid month! " ~ num) }}
{%- endif %}
{%- endmacro month_name %}

{#
The blog templates used to be written in Handlebars, but Tera and Handlebars
escape HTML differently by default:
Tera: &<>"'/
Handlebars: &<>"'`=
To keep the output identical, this macro matches the behavior of Handlebars.
#}
{% macro escape_hbs(input) -%}
{{ input
| replace(from="&", to="&amp;")
| replace(from="<", to="&lt;")
| replace(from=">", to="&gt;")
| replace(from='"', to="&quot;")
| replace(from="'", to="&#x27;")
| replace(from="`", to="&#x60;")
| replace(from="=", to="&#x3D;")
}}
{%- endmacro escape_hbs %}
Loading