1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use std::path::PathBuf;

use rustc_data_structures::fx::FxHashMap;

use crate::externalfiles::ExternalHtml;
use crate::html::format::{Buffer, Print};
use crate::html::render::{ensure_trailing_slash, StylePath};

use askama::Template;

use super::static_files::{StaticFiles, STATIC_FILES};

#[derive(Clone)]
pub(crate) struct Layout {
    pub(crate) logo: String,
    pub(crate) favicon: String,
    pub(crate) external_html: ExternalHtml,
    pub(crate) default_settings: FxHashMap<String, String>,
    pub(crate) krate: String,
    pub(crate) krate_version: String,
    /// The given user css file which allow to customize the generated
    /// documentation theme.
    pub(crate) css_file_extension: Option<PathBuf>,
    /// If true, then scrape-examples.js will be included in the output HTML file
    pub(crate) scrape_examples_extension: bool,
}

pub(crate) struct Page<'a> {
    pub(crate) title: &'a str,
    pub(crate) css_class: &'a str,
    pub(crate) root_path: &'a str,
    pub(crate) static_root_path: Option<&'a str>,
    pub(crate) description: &'a str,
    pub(crate) resource_suffix: &'a str,
    pub(crate) rust_logo: bool,
}

impl<'a> Page<'a> {
    pub(crate) fn get_static_root_path(&self) -> String {
        match self.static_root_path {
            Some(s) => s.to_string(),
            None => format!("{}static.files/", self.root_path),
        }
    }
}

#[derive(Template)]
#[template(path = "page.html")]
struct PageLayout<'a> {
    static_root_path: String,
    page: &'a Page<'a>,
    layout: &'a Layout,

    files: &'static StaticFiles,

    themes: Vec<String>,
    sidebar: String,
    content: String,
    rust_channel: &'static str,
    pub(crate) rustdoc_version: &'a str,
    // same as layout.krate, except on top-level pages like
    // Settings, Help, All Crates, and About Scraped Examples,
    // where these things instead give Rustdoc name and version.
    //
    // These are separate from the variables used for the search
    // engine, because "Rustdoc" isn't necessarily a crate in
    // the current workspace.
    display_krate: &'a str,
    display_krate_with_trailing_slash: String,
    display_krate_version_number: &'a str,
    display_krate_version_extra: &'a str,
}

pub(crate) fn render<T: Print, S: Print>(
    layout: &Layout,
    page: &Page<'_>,
    sidebar: S,
    t: T,
    style_files: &[StylePath],
) -> String {
    let rustdoc_version = rustc_interface::util::version_str!().unwrap_or("unknown version");

    let (display_krate, display_krate_version, display_krate_with_trailing_slash) =
        if page.root_path == "./" {
            // top level pages use Rust branding
            ("Rustdoc", rustdoc_version, String::new())
        } else {
            let display_krate_with_trailing_slash =
                ensure_trailing_slash(&layout.krate).to_string();
            (&layout.krate[..], &layout.krate_version[..], display_krate_with_trailing_slash)
        };
    let static_root_path = page.get_static_root_path();

    // bootstrap passes in parts of the version separated by tabs, but other stuff might use spaces
    let (display_krate_version_number, display_krate_version_extra) =
        display_krate_version.split_once([' ', '\t']).unwrap_or((display_krate_version, ""));

    let mut themes: Vec<String> = style_files.iter().map(|s| s.basename().unwrap()).collect();
    themes.sort();

    let content = Buffer::html().to_display(t); // Note: This must happen before making the sidebar.
    let sidebar = Buffer::html().to_display(sidebar);
    PageLayout {
        static_root_path,
        page,
        layout,
        files: &STATIC_FILES,
        themes,
        sidebar,
        content,
        display_krate,
        display_krate_with_trailing_slash,
        display_krate_version_number,
        display_krate_version_extra,
        rust_channel: *crate::clean::utils::DOC_CHANNEL,
        rustdoc_version,
    }
    .render()
    .unwrap()
}

pub(crate) fn redirect(url: &str) -> String {
    // <script> triggers a redirect before refresh, so this is fine.
    format!(
        r##"<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="refresh" content="0;URL={url}">
    <title>Redirection</title>
</head>
<body>
    <p>Redirecting to <a href="{url}">{url}</a>...</p>
    <script>location.replace("{url}" + location.search + location.hash);</script>
</body>
</html>"##,
        url = url,
    )
}