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,
crate generate_search_filter: bool,
}
+#[derive(Serialize)]
crate struct Page<'a> {
crate title: &'a str,
crate css_class: &'a str,
}
}
+#[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,
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\">☰</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 {