]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/layout.rs
Rollup merge of #85989 - jyn514:gitignore-cleanup, r=ehuss
[rust.git] / src / librustdoc / html / layout.rs
1 use std::path::PathBuf;
2
3 use rustc_data_structures::fx::FxHashMap;
4
5 use crate::externalfiles::ExternalHtml;
6 use crate::html::escape::Escape;
7 use crate::html::format::{Buffer, Print};
8 use crate::html::render::{ensure_trailing_slash, StylePath};
9
10 #[derive(Clone)]
11 crate struct Layout {
12     crate logo: String,
13     crate favicon: String,
14     crate external_html: ExternalHtml,
15     crate default_settings: FxHashMap<String, String>,
16     crate krate: String,
17     /// The given user css file which allow to customize the generated
18     /// documentation theme.
19     crate css_file_extension: Option<PathBuf>,
20     /// If false, the `select` element to have search filtering by crates on rendered docs
21     /// won't be generated.
22     crate generate_search_filter: bool,
23 }
24
25 crate struct Page<'a> {
26     crate title: &'a str,
27     crate css_class: &'a str,
28     crate root_path: &'a str,
29     crate static_root_path: Option<&'a str>,
30     crate description: &'a str,
31     crate keywords: &'a str,
32     crate resource_suffix: &'a str,
33     crate extra_scripts: &'a [&'a str],
34     crate static_extra_scripts: &'a [&'a str],
35 }
36
37 impl<'a> Page<'a> {
38     crate fn get_static_root_path(&self) -> &str {
39         self.static_root_path.unwrap_or(self.root_path)
40     }
41 }
42
43 crate fn render<T: Print, S: Print>(
44     layout: &Layout,
45     page: &Page<'_>,
46     sidebar: S,
47     t: T,
48     style_files: &[StylePath],
49 ) -> String {
50     let static_root_path = page.get_static_root_path();
51     format!(
52         "<!DOCTYPE html>\
53 <html lang=\"en\">\
54 <head>\
55     <meta charset=\"utf-8\">\
56     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
57     <meta name=\"generator\" content=\"rustdoc\">\
58     <meta name=\"description\" content=\"{description}\">\
59     <meta name=\"keywords\" content=\"{keywords}\">\
60     <title>{title}</title>\
61     <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}normalize{suffix}.css\">\
62     <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}rustdoc{suffix}.css\" \
63           id=\"mainThemeStyle\">\
64     {style_files}\
65     <script id=\"default-settings\"{default_settings}></script>\
66     <script src=\"{static_root_path}storage{suffix}.js\"></script>\
67     <script src=\"{root_path}crates{suffix}.js\"></script>\
68     <noscript><link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\"></noscript>\
69     {css_extension}\
70     {favicon}\
71     {in_header}\
72     <style type=\"text/css\">\
73     #crate-search{{background-image:url(\"{static_root_path}down-arrow{suffix}.svg\");}}\
74     </style>\
75 </head>\
76 <body class=\"rustdoc {css_class}\">\
77     <!--[if lte IE 11]>\
78     <div class=\"warning\">\
79         This old browser is unsupported and will most likely display funky \
80         things.\
81     </div>\
82     <![endif]-->\
83     {before_content}\
84     <nav class=\"sidebar\">\
85         <div class=\"sidebar-menu\" role=\"button\">&#9776;</div>\
86         {logo}\
87         {sidebar}\
88     </nav>\
89     <div class=\"theme-picker\">\
90         <button id=\"theme-picker\" aria-label=\"Pick another theme!\" aria-haspopup=\"menu\" title=\"themes\">\
91             <img src=\"{static_root_path}brush{suffix}.svg\" \
92                  width=\"18\" height=\"18\" \
93                  alt=\"Pick another theme!\">\
94         </button>\
95         <div id=\"theme-choices\" role=\"menu\"></div>\
96     </div>\
97     <nav class=\"sub\">\
98         <form class=\"search-form\">\
99             <div class=\"search-container\">\
100                 <div>{filter_crates}\
101                     <input class=\"search-input\" name=\"search\" \
102                            disabled \
103                            autocomplete=\"off\" \
104                            spellcheck=\"false\" \
105                            placeholder=\"Click or press ‘S’ to search, ‘?’ for more options…\" \
106                            type=\"search\">\
107                 </div>\
108                 <button type=\"button\" id=\"help-button\" title=\"help\">?</button>\
109                 <a id=\"settings-menu\" href=\"{root_path}settings.html\" title=\"settings\">\
110                     <img src=\"{static_root_path}wheel{suffix}.svg\" \
111                          width=\"18\" height=\"18\" \
112                          alt=\"Change settings\">\
113                 </a>\
114             </div>\
115         </form>\
116     </nav>\
117     <section id=\"main\" class=\"content\">{content}</section>\
118     <section id=\"search\" class=\"content hidden\"></section>\
119     {after_content}\
120     <div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\" \
121        data-search-index-js=\"{root_path}search-index{suffix}.js\" \
122        data-search-js=\"{static_root_path}search{suffix}.js\"></div>\
123     <script src=\"{static_root_path}main{suffix}.js\"></script>\
124     {extra_scripts}\
125 </body>\
126 </html>",
127         css_extension = if layout.css_file_extension.is_some() {
128             format!(
129                 "<link rel=\"stylesheet\" \
130                        type=\"text/css\" \
131                        href=\"{static_root_path}theme{suffix}.css\">",
132                 static_root_path = static_root_path,
133                 suffix = page.resource_suffix
134             )
135         } else {
136             String::new()
137         },
138         content = Buffer::html().to_display(t),
139         static_root_path = static_root_path,
140         root_path = page.root_path,
141         css_class = page.css_class,
142         logo = {
143             if layout.logo.is_empty() {
144                 format!(
145                     "<a href='{root}{path}index.html'>\
146                      <div class='logo-container rust-logo'>\
147                      <img src='{static_root_path}rust-logo{suffix}.png' alt='logo'></div></a>",
148                     root = page.root_path,
149                     path = ensure_trailing_slash(&layout.krate),
150                     static_root_path = static_root_path,
151                     suffix = page.resource_suffix
152                 )
153             } else {
154                 format!(
155                     "<a href='{root}{path}index.html'>\
156                      <div class='logo-container'><img src='{logo}' alt='logo'></div></a>",
157                     root = page.root_path,
158                     path = ensure_trailing_slash(&layout.krate),
159                     logo = layout.logo
160                 )
161             }
162         },
163         title = page.title,
164         description = Escape(page.description),
165         keywords = page.keywords,
166         favicon = if layout.favicon.is_empty() {
167             format!(
168                 r##"<link rel="icon" type="image/svg+xml" href="{static_root_path}favicon{suffix}.svg">
169 <link rel="alternate icon" type="image/png" href="{static_root_path}favicon-16x16{suffix}.png">
170 <link rel="alternate icon" type="image/png" href="{static_root_path}favicon-32x32{suffix}.png">"##,
171                 static_root_path = static_root_path,
172                 suffix = page.resource_suffix
173             )
174         } else {
175             format!(r#"<link rel="shortcut icon" href="{}">"#, layout.favicon)
176         },
177         in_header = layout.external_html.in_header,
178         before_content = layout.external_html.before_content,
179         after_content = layout.external_html.after_content,
180         sidebar = Buffer::html().to_display(sidebar),
181         krate = layout.krate,
182         default_settings = layout
183             .default_settings
184             .iter()
185             .map(|(k, v)| format!(r#" data-{}="{}""#, k.replace('-', "_"), Escape(v)))
186             .collect::<String>(),
187         style_files = style_files
188             .iter()
189             .filter_map(|t| {
190                 if let Some(stem) = t.path.file_stem() { Some((stem, t.disabled)) } else { None }
191             })
192             .filter_map(|t| {
193                 if let Some(path) = t.0.to_str() { Some((path, t.1)) } else { None }
194             })
195             .map(|t| format!(
196                 r#"<link rel="stylesheet" type="text/css" href="{}.css" {} {}>"#,
197                 Escape(&format!("{}{}{}", static_root_path, t.0, page.resource_suffix)),
198                 if t.1 { "disabled" } else { "" },
199                 if t.0 == "light" { "id=\"themeStyle\"" } else { "" }
200             ))
201             .collect::<String>(),
202         suffix = page.resource_suffix,
203         extra_scripts = page
204             .static_extra_scripts
205             .iter()
206             .map(|e| {
207                 format!(
208                     "<script src=\"{static_root_path}{extra_script}.js\"></script>",
209                     static_root_path = static_root_path,
210                     extra_script = e
211                 )
212             })
213             .chain(page.extra_scripts.iter().map(|e| {
214                 format!(
215                     "<script src=\"{root_path}{extra_script}.js\"></script>",
216                     root_path = page.root_path,
217                     extra_script = e
218                 )
219             }))
220             .collect::<String>(),
221         filter_crates = if layout.generate_search_filter {
222             "<select id=\"crate-search\">\
223                  <option value=\"All crates\">All crates</option>\
224              </select>"
225         } else {
226             ""
227         },
228     )
229 }
230
231 crate fn redirect(url: &str) -> String {
232     // <script> triggers a redirect before refresh, so this is fine.
233     format!(
234         r##"<!DOCTYPE html>
235 <html lang="en">
236 <head>
237     <meta http-equiv="refresh" content="0;URL={url}">
238     <title>Redirection</title>
239 </head>
240 <body>
241     <p>Redirecting to <a href="{url}">{url}</a>...</p>
242     <script>location.replace("{url}" + location.search + location.hash);</script>
243 </body>
244 </html>"##,
245         url = url,
246     )
247 }