Skip to content
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

Use Tera templates for rustdoc. #86157

Merged
merged 1 commit into from
Jun 21, 2021
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
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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh oof I forgot tera uses globwalk :( it's extremely slow: rust-lang/docs.rs@9c9647d

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't actually use globwalk in this PR, we are manually loading files the same way the linked docs.rs PR does.

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