Skip to content

Commit

Permalink
Use Tera templates for rustdoc.
Browse files Browse the repository at this point in the history
Replaces a format!() call in layout::render with a template
expansion. Introduces a `templates` field in SharedContext so parts
of rustdoc can share pre-rendered templates.

This currently builds in a copy of the single template available, like
with static files. However, future work can make this live-loadable with
a perma-unstable flag, to make rustdoc developers' work easier.
  • Loading branch information
jsha committed Jun 21, 2021
1 parent 50a4072 commit cd0f931
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 176 deletions.
27 changes: 27 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1482,6 +1482,17 @@ dependencies = [
"regex",
]

[[package]]
name = "globwalk"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
dependencies = [
"bitflags",
"ignore",
"walkdir",
]

[[package]]
name = "gsgdt"
version = "0.1.2"
Expand Down Expand Up @@ -4518,6 +4529,7 @@ dependencies = [
"serde_json",
"smallvec",
"tempfile",
"tera",
"tracing",
"tracing-subscriber",
"tracing-tree",
Expand Down Expand Up @@ -5099,6 +5111,21 @@ dependencies = [
"utf-8",
]

[[package]]
name = "tera"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81060acb882480c8793782eb96bc86f5c83d2fc7175ad46c375c6956ef7afa62"
dependencies = [
"globwalk",
"lazy_static",
"pest",
"pest_derive",
"regex",
"serde",
"serde_json",
]

[[package]]
name = "term"
version = "0.0.0"
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ regex = "1"
rustdoc-json-types = { path = "../rustdoc-json-types" }
tracing = "0.1"
tracing-tree = "0.1.9"
tera = { version = "1.10.0", default-features = false }

[dependencies.tracing-subscriber]
version = "0.2.13"
Expand Down
4 changes: 3 additions & 1 deletion src/librustdoc/externalfiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use std::fs;
use std::path::Path;
use std::str;

#[derive(Clone, Debug)]
use serde::Serialize;

#[derive(Clone, Debug, Serialize)]
crate struct ExternalHtml {
/// Content that will be included inline in the <head> section of a
/// rendered Markdown file or generated documentation
Expand Down
214 changes: 40 additions & 174 deletions src/librustdoc/html/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use crate::html::escape::Escape;
use crate::html::format::{Buffer, Print};
use crate::html::render::{ensure_trailing_slash, StylePath};

#[derive(Clone)]
use serde::Serialize;

#[derive(Clone, Serialize)]
crate struct Layout {
crate logo: String,
crate favicon: String,
Expand All @@ -22,6 +24,7 @@ crate struct Layout {
crate generate_search_filter: bool,
}

#[derive(Serialize)]
crate struct Page<'a> {
crate title: &'a str,
crate css_class: &'a str,
Expand All @@ -40,192 +43,55 @@ impl<'a> Page<'a> {
}
}

#[derive(Serialize)]
struct PageLayout<'a> {
static_root_path: &'a str,
page: &'a Page<'a>,
layout: &'a Layout,
style_files: String,
sidebar: String,
content: String,
krate_with_trailing_slash: String,
}

crate fn render<T: Print, S: Print>(
templates: &tera::Tera,
layout: &Layout,
page: &Page<'_>,
sidebar: S,
t: T,
style_files: &[StylePath],
) -> String {
let static_root_path = page.get_static_root_path();
format!(
"<!DOCTYPE html>\
<html lang=\"en\">\
<head>\
<meta charset=\"utf-8\">\
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
<meta name=\"generator\" content=\"rustdoc\">\
<meta name=\"description\" content=\"{description}\">\
<meta name=\"keywords\" content=\"{keywords}\">\
<title>{title}</title>\
<link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}normalize{suffix}.css\">\
<link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}rustdoc{suffix}.css\" \
id=\"mainThemeStyle\">\
{style_files}\
<script id=\"default-settings\"{default_settings}></script>\
<script src=\"{static_root_path}storage{suffix}.js\"></script>\
<script src=\"{root_path}crates{suffix}.js\"></script>\
<noscript><link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\"></noscript>\
{css_extension}\
{favicon}\
{in_header}\
<style type=\"text/css\">\
#crate-search{{background-image:url(\"{static_root_path}down-arrow{suffix}.svg\");}}\
</style>\
</head>\
<body class=\"rustdoc {css_class}\">\
<!--[if lte IE 11]>\
<div class=\"warning\">\
This old browser is unsupported and will most likely display funky \
things.\
</div>\
<![endif]-->\
{before_content}\
<nav class=\"sidebar\">\
<div class=\"sidebar-menu\" role=\"button\">&#9776;</div>\
{logo}\
{sidebar}\
</nav>\
<div class=\"theme-picker\">\
<button id=\"theme-picker\" aria-label=\"Pick another theme!\" aria-haspopup=\"menu\" title=\"themes\">\
<img src=\"{static_root_path}brush{suffix}.svg\" \
width=\"18\" height=\"18\" \
alt=\"Pick another theme!\">\
</button>\
<div id=\"theme-choices\" role=\"menu\"></div>\
</div>\
<nav class=\"sub\">\
<form class=\"search-form\">\
<div class=\"search-container\">\
<div>{filter_crates}\
<input class=\"search-input\" name=\"search\" \
disabled \
autocomplete=\"off\" \
spellcheck=\"false\" \
placeholder=\"Click or press ‘S’ to search, ‘?’ for more options…\" \
type=\"search\">\
</div>\
<button type=\"button\" id=\"help-button\" title=\"help\">?</button>\
<a id=\"settings-menu\" href=\"{root_path}settings.html\" title=\"settings\">\
<img src=\"{static_root_path}wheel{suffix}.svg\" \
width=\"18\" height=\"18\" \
alt=\"Change settings\">\
</a>\
</div>\
</form>\
</nav>\
<section id=\"main\" class=\"content\">{content}</section>\
<section id=\"search\" class=\"content hidden\"></section>\
{after_content}\
<div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\" \
data-search-index-js=\"{root_path}search-index{suffix}.js\" \
data-search-js=\"{static_root_path}search{suffix}.js\"></div>\
<script src=\"{static_root_path}main{suffix}.js\"></script>\
{extra_scripts}\
</body>\
</html>",
css_extension = if layout.css_file_extension.is_some() {
let krate_with_trailing_slash = ensure_trailing_slash(&layout.krate).to_string();
let style_files = style_files
.iter()
.filter_map(|t| {
if let Some(stem) = t.path.file_stem() { Some((stem, t.disabled)) } else { None }
})
.filter_map(|t| if let Some(path) = t.0.to_str() { Some((path, t.1)) } else { None })
.map(|t| {
format!(
"<link rel=\"stylesheet\" \
type=\"text/css\" \
href=\"{static_root_path}theme{suffix}.css\">",
static_root_path = static_root_path,
suffix = page.resource_suffix
)
} else {
String::new()
},
content = Buffer::html().to_display(t),
static_root_path = static_root_path,
root_path = page.root_path,
css_class = page.css_class,
logo = {
if layout.logo.is_empty() {
format!(
"<a href='{root}{path}index.html'>\
<div class='logo-container rust-logo'>\
<img src='{static_root_path}rust-logo{suffix}.png' alt='logo'></div></a>",
root = page.root_path,
path = ensure_trailing_slash(&layout.krate),
static_root_path = static_root_path,
suffix = page.resource_suffix
)
} else {
format!(
"<a href='{root}{path}index.html'>\
<div class='logo-container'><img src='{logo}' alt='logo'></div></a>",
root = page.root_path,
path = ensure_trailing_slash(&layout.krate),
logo = layout.logo
)
}
},
title = page.title,
description = Escape(page.description),
keywords = page.keywords,
favicon = if layout.favicon.is_empty() {
format!(
r##"<link rel="icon" type="image/svg+xml" href="{static_root_path}favicon{suffix}.svg">
<link rel="alternate icon" type="image/png" href="{static_root_path}favicon-16x16{suffix}.png">
<link rel="alternate icon" type="image/png" href="{static_root_path}favicon-32x32{suffix}.png">"##,
static_root_path = static_root_path,
suffix = page.resource_suffix
)
} else {
format!(r#"<link rel="shortcut icon" href="{}">"#, layout.favicon)
},
in_header = layout.external_html.in_header,
before_content = layout.external_html.before_content,
after_content = layout.external_html.after_content,
sidebar = Buffer::html().to_display(sidebar),
krate = layout.krate,
default_settings = layout
.default_settings
.iter()
.map(|(k, v)| format!(r#" data-{}="{}""#, k.replace('-', "_"), Escape(v)))
.collect::<String>(),
style_files = style_files
.iter()
.filter_map(|t| {
if let Some(stem) = t.path.file_stem() { Some((stem, t.disabled)) } else { None }
})
.filter_map(|t| {
if let Some(path) = t.0.to_str() { Some((path, t.1)) } else { None }
})
.map(|t| format!(
r#"<link rel="stylesheet" type="text/css" href="{}.css" {} {}>"#,
Escape(&format!("{}{}{}", static_root_path, t.0, page.resource_suffix)),
if t.1 { "disabled" } else { "" },
if t.0 == "light" { "id=\"themeStyle\"" } else { "" }
))
.collect::<String>(),
suffix = page.resource_suffix,
extra_scripts = page
.static_extra_scripts
.iter()
.map(|e| {
format!(
"<script src=\"{static_root_path}{extra_script}.js\"></script>",
static_root_path = static_root_path,
extra_script = e
)
})
.chain(page.extra_scripts.iter().map(|e| {
format!(
"<script src=\"{root_path}{extra_script}.js\"></script>",
root_path = page.root_path,
extra_script = e
)
}))
.collect::<String>(),
filter_crates = if layout.generate_search_filter {
"<select id=\"crate-search\">\
<option value=\"All crates\">All crates</option>\
</select>"
} else {
""
},
)
)
})
.collect::<String>();
let content = Buffer::html().to_display(t); // Note: This must happen before making the sidebar.
let sidebar = Buffer::html().to_display(sidebar);
let teractx = tera::Context::from_serialize(PageLayout {
static_root_path,
page,
layout,
style_files,
sidebar,
content,
krate_with_trailing_slash,
})
.unwrap();
templates.render("page.html", &teractx).unwrap()
}

crate fn redirect(url: &str) -> String {
Expand Down
14 changes: 14 additions & 0 deletions src/librustdoc/html/render/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::error::Error as StdError;
use std::io;
use std::path::{Path, PathBuf};
use std::rc::Rc;
Expand Down Expand Up @@ -29,6 +30,7 @@ use crate::formats::FormatRenderer;
use crate::html::escape::Escape;
use crate::html::format::Buffer;
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
use crate::html::static_files::PAGE;
use crate::html::{layout, sources};

/// Major driving force in all rustdoc rendering. This contains information
Expand Down Expand Up @@ -121,6 +123,8 @@ crate struct SharedContext<'tcx> {
/// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of
/// the crate.
redirections: Option<RefCell<FxHashMap<String, String>>>,

pub(crate) templates: tera::Tera,
}

impl SharedContext<'_> {
Expand Down Expand Up @@ -218,6 +222,7 @@ impl<'tcx> Context<'tcx> {

if !self.render_redirect_pages {
layout::render(
&self.shared.templates,
&self.shared.layout,
&page,
|buf: &mut _| print_sidebar(self, it, buf),
Expand Down Expand Up @@ -408,6 +413,12 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
let mut issue_tracker_base_url = None;
let mut include_sources = true;

let mut templates = tera::Tera::default();
templates.add_raw_template("page.html", PAGE).map_err(|e| Error {
file: "page.html".into(),
error: format!("{}: {}", e, e.source().map(|e| e.to_string()).unwrap_or_default()),
})?;

// Crawl the crate attributes looking for attributes which control how we're
// going to emit HTML
for attr in krate.module.attrs.lists(sym::doc) {
Expand Down Expand Up @@ -454,6 +465,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
errors: receiver,
redirections: if generate_redirect_map { Some(Default::default()) } else { None },
show_type_layout,
templates,
};

// Add the default themes to the `Vec` of stylepaths
Expand Down Expand Up @@ -540,6 +552,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
};
let all = self.shared.all.replace(AllTypes::new());
let v = layout::render(
&self.shared.templates,
&self.shared.layout,
&page,
sidebar,
Expand All @@ -557,6 +570,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
let sidebar = "<p class=\"location\">Settings</p><div class=\"sidebar-elems\"></div>";
style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false });
let v = layout::render(
&self.shared.templates,
&self.shared.layout,
&page,
sidebar,
Expand Down
9 changes: 8 additions & 1 deletion src/librustdoc/html/render/write_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,14 @@ pub(super) fn write_shared(
})
.collect::<String>()
);
let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files);
let v = layout::render(
&cx.shared.templates,
&cx.shared.layout,
&page,
"",
content,
&cx.shared.style_files,
);
cx.shared.fs.write(&dst, v.as_bytes())?;
}
}
Expand Down
Loading

0 comments on commit cd0f931

Please sign in to comment.