]>
Commit | Line | Data |
---|---|---|
1a4d82fc | 1 | use std::fmt; |
c34b1796 | 2 | use std::io; |
2c00a5a8 | 3 | use std::path::PathBuf; |
1a4d82fc | 4 | |
9fa01778 XL |
5 | use crate::externalfiles::ExternalHtml; |
6 | use crate::html::render::SlashChecker; | |
1a4d82fc JJ |
7 | |
8 | #[derive(Clone)] | |
9 | pub struct Layout { | |
10 | pub logo: String, | |
11 | pub favicon: String, | |
12 | pub external_html: ExternalHtml, | |
13 | pub krate: String, | |
1a4d82fc JJ |
14 | } |
15 | ||
16 | pub struct Page<'a> { | |
17 | pub title: &'a str, | |
9e0c209e | 18 | pub css_class: &'a str, |
1a4d82fc | 19 | pub root_path: &'a str, |
0731742a | 20 | pub static_root_path: Option<&'a str>, |
1a4d82fc | 21 | pub description: &'a str, |
54a0048b | 22 | pub keywords: &'a str, |
0531ce1d | 23 | pub resource_suffix: &'a str, |
0731742a XL |
24 | pub extra_scripts: &'a [&'a str], |
25 | pub static_extra_scripts: &'a [&'a str], | |
1a4d82fc JJ |
26 | } |
27 | ||
85aaf69f | 28 | pub fn render<T: fmt::Display, S: fmt::Display>( |
0731742a XL |
29 | dst: &mut dyn io::Write, |
30 | layout: &Layout, | |
9fa01778 | 31 | page: &Page<'_>, |
0731742a XL |
32 | sidebar: &S, |
33 | t: &T, | |
34 | css_file_extension: bool, | |
35 | themes: &[PathBuf], | |
36 | generate_search_filter: bool, | |
37 | ) -> io::Result<()> { | |
38 | let static_root_path = page.static_root_path.unwrap_or(page.root_path); | |
1a4d82fc | 39 | write!(dst, |
83c7162d XL |
40 | "<!DOCTYPE html>\ |
41 | <html lang=\"en\">\ | |
42 | <head>\ | |
43 | <meta charset=\"utf-8\">\ | |
44 | <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\ | |
45 | <meta name=\"generator\" content=\"rustdoc\">\ | |
46 | <meta name=\"description\" content=\"{description}\">\ | |
47 | <meta name=\"keywords\" content=\"{keywords}\">\ | |
48 | <title>{title}</title>\ | |
0731742a XL |
49 | <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}normalize{suffix}.css\">\ |
50 | <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}rustdoc{suffix}.css\" \ | |
83c7162d XL |
51 | id=\"mainThemeStyle\">\ |
52 | {themes}\ | |
0731742a XL |
53 | <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}dark{suffix}.css\">\ |
54 | <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}light{suffix}.css\" \ | |
83c7162d | 55 | id=\"themeStyle\">\ |
0731742a XL |
56 | <script src=\"{static_root_path}storage{suffix}.js\"></script>\ |
57 | <noscript><link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\"></noscript>\ | |
83c7162d XL |
58 | {css_extension}\ |
59 | {favicon}\ | |
60 | {in_header}\ | |
0731742a XL |
61 | <style type=\"text/css\">\ |
62 | #crate-search{{background-image:url(\"{static_root_path}down-arrow{suffix}.svg\");}}\ | |
63 | </style>\ | |
83c7162d XL |
64 | </head>\ |
65 | <body class=\"rustdoc {css_class}\">\ | |
66 | <!--[if lte IE 8]>\ | |
67 | <div class=\"warning\">\ | |
68 | This old browser is unsupported and will most likely display funky \ | |
69 | things.\ | |
70 | </div>\ | |
71 | <![endif]-->\ | |
72 | {before_content}\ | |
73 | <nav class=\"sidebar\">\ | |
74 | <div class=\"sidebar-menu\">☰</div>\ | |
75 | {logo}\ | |
76 | {sidebar}\ | |
77 | </nav>\ | |
78 | <div class=\"theme-picker\">\ | |
79 | <button id=\"theme-picker\" aria-label=\"Pick another theme!\">\ | |
0731742a XL |
80 | <img src=\"{static_root_path}brush{suffix}.svg\" \ |
81 | width=\"18\" \ | |
82 | alt=\"Pick another theme!\">\ | |
83c7162d XL |
83 | </button>\ |
84 | <div id=\"theme-choices\"></div>\ | |
85 | </div>\ | |
0731742a | 86 | <script src=\"{static_root_path}theme{suffix}.js\"></script>\ |
83c7162d XL |
87 | <nav class=\"sub\">\ |
88 | <form class=\"search-form js-only\">\ | |
89 | <div class=\"search-container\">\ | |
0731742a XL |
90 | <div>{filter_crates}\ |
91 | <input class=\"search-input\" name=\"search\" \ | |
92 | autocomplete=\"off\" \ | |
93 | spellcheck=\"false\" \ | |
94 | placeholder=\"Click or press ‘S’ to search, ‘?’ for more options…\" \ | |
95 | type=\"search\">\ | |
96 | </div>\ | |
83c7162d | 97 | <a id=\"settings-menu\" href=\"{root_path}settings.html\">\ |
0731742a XL |
98 | <img src=\"{static_root_path}wheel{suffix}.svg\" \ |
99 | width=\"18\" \ | |
100 | alt=\"Change settings\">\ | |
83c7162d XL |
101 | </a>\ |
102 | </div>\ | |
103 | </form>\ | |
104 | </nav>\ | |
105 | <section id=\"main\" class=\"content\">{content}</section>\ | |
106 | <section id=\"search\" class=\"content hidden\"></section>\ | |
107 | <section class=\"footer\"></section>\ | |
108 | <aside id=\"help\" class=\"hidden\">\ | |
109 | <div>\ | |
110 | <h1 class=\"hidden\">Help</h1>\ | |
111 | <div class=\"shortcuts\">\ | |
112 | <h2>Keyboard Shortcuts</h2>\ | |
113 | <dl>\ | |
114 | <dt><kbd>?</kbd></dt>\ | |
115 | <dd>Show this help dialog</dd>\ | |
116 | <dt><kbd>S</kbd></dt>\ | |
117 | <dd>Focus the search field</dd>\ | |
118 | <dt><kbd>↑</kbd></dt>\ | |
119 | <dd>Move up in search results</dd>\ | |
120 | <dt><kbd>↓</kbd></dt>\ | |
121 | <dd>Move down in search results</dd>\ | |
122 | <dt><kbd>↹</kbd></dt>\ | |
123 | <dd>Switch tab</dd>\ | |
124 | <dt><kbd>⏎</kbd></dt>\ | |
125 | <dd>Go to active search result</dd>\ | |
126 | <dt><kbd>+</kbd></dt>\ | |
127 | <dd>Expand all sections</dd>\ | |
128 | <dt><kbd>-</kbd></dt>\ | |
129 | <dd>Collapse all sections</dd>\ | |
130 | </dl>\ | |
131 | </div>\ | |
132 | <div class=\"infos\">\ | |
133 | <h2>Search Tricks</h2>\ | |
134 | <p>\ | |
0731742a | 135 | Prefix searches with a type followed by a colon (e.g., \ |
83c7162d XL |
136 | <code>fn:</code>) to restrict the search to a given type.\ |
137 | </p>\ | |
138 | <p>\ | |
139 | Accepted types are: <code>fn</code>, <code>mod</code>, \ | |
140 | <code>struct</code>, <code>enum</code>, \ | |
141 | <code>trait</code>, <code>type</code>, <code>macro</code>, \ | |
142 | and <code>const</code>.\ | |
143 | </p>\ | |
144 | <p>\ | |
0731742a | 145 | Search functions by type signature (e.g., \ |
83c7162d XL |
146 | <code>vec -> usize</code> or <code>* -> vec</code>)\ |
147 | </p>\ | |
148 | <p>\ | |
0731742a | 149 | Search multiple things at once by splitting your query with comma (e.g., \ |
83c7162d XL |
150 | <code>str,u8</code> or <code>String,struct:Vec,test</code>)\ |
151 | </p>\ | |
152 | </div>\ | |
153 | </div>\ | |
154 | </aside>\ | |
155 | {after_content}\ | |
156 | <script>\ | |
157 | window.rootPath = \"{root_path}\";\ | |
158 | window.currentCrate = \"{krate}\";\ | |
159 | </script>\ | |
160 | <script src=\"{root_path}aliases.js\"></script>\ | |
0731742a XL |
161 | <script src=\"{static_root_path}main{suffix}.js\"></script>\ |
162 | {static_extra_scripts}\ | |
a1dfa0c6 | 163 | {extra_scripts}\ |
83c7162d XL |
164 | <script defer src=\"{root_path}search-index.js\"></script>\ |
165 | </body>\ | |
166 | </html>", | |
54a0048b | 167 | css_extension = if css_file_extension { |
0731742a XL |
168 | format!("<link rel=\"stylesheet\" \ |
169 | type=\"text/css\" \ | |
170 | href=\"{static_root_path}theme{suffix}.css\">", | |
171 | static_root_path = static_root_path, | |
0531ce1d | 172 | suffix=page.resource_suffix) |
54a0048b | 173 | } else { |
b7449926 | 174 | String::new() |
54a0048b | 175 | }, |
1a4d82fc | 176 | content = *t, |
0731742a | 177 | static_root_path = static_root_path, |
1a4d82fc | 178 | root_path = page.root_path, |
9e0c209e | 179 | css_class = page.css_class, |
9fa01778 XL |
180 | logo = { |
181 | let p = format!("{}{}", page.root_path, layout.krate); | |
182 | let p = SlashChecker(&p); | |
183 | if layout.logo.is_empty() { | |
184 | format!("<a href='{path}index.html'>\ | |
185 | <img src='{static_root_path}rust-logo{suffix}.png' \ | |
186 | alt='logo' width='100'></a>", | |
187 | path=p, | |
188 | static_root_path=static_root_path, | |
189 | suffix=page.resource_suffix) | |
190 | } else { | |
191 | format!("<a href='{}index.html'>\ | |
192 | <img src='{}' alt='logo' width='100'></a>", | |
193 | p, | |
194 | layout.logo) | |
195 | } | |
1a4d82fc JJ |
196 | }, |
197 | title = page.title, | |
198 | description = page.description, | |
199 | keywords = page.keywords, | |
9346a6ac | 200 | favicon = if layout.favicon.is_empty() { |
9fa01778 XL |
201 | format!(r#"<link rel="shortcut icon" href="{static_root_path}favicon{suffix}.ico">"#, |
202 | static_root_path=static_root_path, | |
203 | suffix=page.resource_suffix) | |
1a4d82fc JJ |
204 | } else { |
205 | format!(r#"<link rel="shortcut icon" href="{}">"#, layout.favicon) | |
206 | }, | |
207 | in_header = layout.external_html.in_header, | |
208 | before_content = layout.external_html.before_content, | |
209 | after_content = layout.external_html.after_content, | |
210 | sidebar = *sidebar, | |
211 | krate = layout.krate, | |
2c00a5a8 XL |
212 | themes = themes.iter() |
213 | .filter_map(|t| t.file_stem()) | |
214 | .filter_map(|t| t.to_str()) | |
83c7162d | 215 | .map(|t| format!(r#"<link rel="stylesheet" type="text/css" href="{}{}{}.css">"#, |
0731742a | 216 | static_root_path, |
83c7162d XL |
217 | t, |
218 | page.resource_suffix)) | |
2c00a5a8 | 219 | .collect::<String>(), |
0531ce1d | 220 | suffix=page.resource_suffix, |
0731742a XL |
221 | static_extra_scripts=page.static_extra_scripts.iter().map(|e| { |
222 | format!("<script src=\"{static_root_path}{extra_script}.js\"></script>", | |
223 | static_root_path=static_root_path, | |
224 | extra_script=e) | |
225 | }).collect::<String>(), | |
226 | extra_scripts=page.extra_scripts.iter().map(|e| { | |
a1dfa0c6 XL |
227 | format!("<script src=\"{root_path}{extra_script}.js\"></script>", |
228 | root_path=page.root_path, | |
229 | extra_script=e) | |
230 | }).collect::<String>(), | |
0731742a XL |
231 | filter_crates=if generate_search_filter { |
232 | "<select id=\"crate-search\">\ | |
233 | <option value=\"All crates\">All crates</option>\ | |
234 | </select>" | |
235 | } else { | |
236 | "" | |
237 | }, | |
1a4d82fc JJ |
238 | ) |
239 | } | |
240 | ||
8faf50e0 | 241 | pub fn redirect(dst: &mut dyn io::Write, url: &str) -> io::Result<()> { |
1a4d82fc JJ |
242 | // <script> triggers a redirect before refresh, so this is fine. |
243 | write!(dst, | |
244 | r##"<!DOCTYPE html> | |
245 | <html lang="en"> | |
246 | <head> | |
247 | <meta http-equiv="refresh" content="0;URL={url}"> | |
248 | </head> | |
249 | <body> | |
250 | <p>Redirecting to <a href="{url}">{url}</a>...</p> | |
251 | <script>location.replace("{url}" + location.search + location.hash);</script> | |
252 | </body> | |
253 | </html>"##, | |
254 | url = url, | |
255 | ) | |
256 | } |