Skip to content

Commit 32102ce

Browse files
authored
Merge pull request #217 from jonas-schievink/navifix
Put the navigation back!
2 parents 07896a9 + c85386c commit 32102ce

File tree

10 files changed

+300
-39
lines changed

10 files changed

+300
-39
lines changed

Cargo.lock

Lines changed: 173 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ badge = { version = "0", path = "src/web/badge" }
2828
error-chain = "0.10"
2929
comrak = { version = "0.2.10", default-features = false }
3030
toml = "0.4"
31+
html5ever = "0.22"
3132

3233
# iron dependencies
3334
iron = "0.5"

Vagrantfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Vagrant.configure("2") do |config|
4747
############################################################
4848
lxc-attach -n cratesfyi-container -- apt-get update
4949
lxc-attach -n cratesfyi-container -- apt-get install -y --no-install-recommends curl ca-certificates binutils gcc libc6-dev libmagic1
50-
lxc-attach -n cratesfyi-container -- su - cratesfyi -c 'curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly-2018-03-25'
50+
lxc-attach -n cratesfyi-container -- su - cratesfyi -c 'curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly-2018-06-03'
5151
5252
############################################################
5353
# Creating rustc links for cratesfyi user #

src/docbuilder/chroot_builder.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::path::PathBuf;
1010
use std::fs::remove_dir_all;
1111
use postgres::Connection;
1212
use rustc_serialize::json::Json;
13-
use error::Result;
13+
use error::{Result, ResultExt};
1414

1515

1616
/// List of targets supported by docs.rs
@@ -398,13 +398,15 @@ impl DocBuilder {
398398
let file_name = format!("{}-{}.{}", spl[0], rustc_version, spl[1]);
399399
let source_path = source.join(&file_name);
400400
let destination_path = destination.join(&file_name);
401-
try!(copy(source_path, destination_path));
401+
try!(copy(&source_path, &destination_path)
402+
.chain_err(|| format!("couldn't copy '{}' to '{}'", source_path.display(), destination_path.display())));
402403
}
403404

404405
for file in files.1.iter() {
405406
let source_path = source.join(file);
406407
let destination_path = destination.join(file);
407-
try!(copy(source_path, destination_path));
408+
try!(copy(&source_path, &destination_path)
409+
.chain_err(|| format!("couldn't copy '{}' to '{}'", source_path.display(), destination_path.display())));
408410
}
409411

410412
let conn = try!(connect_db());

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ extern crate badge;
2828
extern crate crates_index_diff;
2929
extern crate git2;
3030
extern crate toml;
31+
extern crate html5ever;
3132

3233
pub use self::docbuilder::DocBuilder;
3334
pub use self::docbuilder::ChrootBuilderResult;

src/utils/html.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use error::{Result, Error};
2+
3+
use html5ever::serialize::{serialize, SerializeOpts};
4+
use html5ever::rcdom::{RcDom, NodeData, Handle};
5+
use html5ever::driver::{parse_document, ParseOpts};
6+
use html5ever::tendril::TendrilSink;
7+
8+
/// Extracts the contents of the `<head>` and `<body>` tags from an HTML document.
9+
pub fn extract_head_and_body(html: &str) -> Result<(String, String)> {
10+
let parser = parse_document(RcDom::default(), ParseOpts::default());
11+
let dom = parser.one(html);
12+
13+
let (head, body) = extract_from_rcdom(&dom)?;
14+
15+
Ok((stringify(head), stringify(body)))
16+
}
17+
18+
fn extract_from_rcdom(dom: &RcDom) -> Result<(Handle, Handle)> {
19+
let mut worklist = vec![dom.document.clone()];
20+
let (mut head, mut body) = (None, None);
21+
22+
while let Some(handle) = worklist.pop() {
23+
match handle.data {
24+
NodeData::Element { ref name, .. } => match name.local.as_ref() {
25+
"head" => {
26+
if head.is_some() {
27+
return Err("duplicate <head> tag".into());
28+
} else {
29+
head = Some(handle.clone());
30+
}
31+
}
32+
"body" => {
33+
if body.is_some() {
34+
return Err("duplicate <body> tag".into());
35+
} else {
36+
body = Some(handle.clone());
37+
}
38+
}
39+
_ => {} // do nothing
40+
}
41+
_ => {} // do nothing
42+
}
43+
44+
worklist.extend(handle.children.borrow().iter().cloned());
45+
}
46+
47+
let head = head.ok_or_else(|| Error::from("couldn't find <head> tag in rustdoc output"))?;
48+
let body = body.ok_or_else(|| Error::from("couldn't find <body> tag in rustdoc output"))?;
49+
Ok((head, body))
50+
}
51+
52+
fn stringify(node: Handle) -> String {
53+
let mut vec = Vec::new();
54+
serialize(&mut vec, &node, SerializeOpts::default())
55+
.expect("serializing into buffer failed");
56+
57+
String::from_utf8(vec).expect("html5ever returned non-utf8 data")
58+
}

src/utils/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub use self::github_updater::github_updater;
77
pub use self::release_activity_updater::update_release_activity;
88
pub use self::daemon::start_daemon;
99
pub use self::rustc_version::{parse_rustc_version, get_current_versions, command_result};
10+
pub use self::html::extract_head_and_body;
1011

1112
mod github_updater;
1213
mod build_doc;
@@ -15,3 +16,4 @@ mod release_activity_updater;
1516
mod daemon;
1617
mod pubsubhubbub;
1718
mod rustc_version;
19+
mod html;

src/web/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const OPENSEARCH_XML: &'static [u8] = include_bytes!("opensearch.xml");
6969

7070

7171
struct CratesfyiHandler {
72+
shared_resource_handler: Box<Handler>,
7273
router_handler: Box<Handler>,
7374
database_file_handler: Box<Handler>,
7475
static_handler: Box<Handler>,
@@ -186,12 +187,14 @@ impl CratesfyiHandler {
186187
rustdoc::rustdoc_html_server_handler,
187188
"crate_version_target_html");
188189

190+
let shared_resources = Self::chain(rustdoc::SharedResourceHandler);
189191
let router_chain = Self::chain(router);
190192
let prefix = PathBuf::from(env::var("CRATESFYI_PREFIX").unwrap()).join("public_html");
191193
let static_handler = Static::new(prefix)
192194
.cache(Duration::from_secs(STATIC_FILE_CACHE_DURATION));
193195

194196
CratesfyiHandler {
197+
shared_resource_handler: Box::new(shared_resources),
195198
router_handler: Box::new(router_chain),
196199
database_file_handler: Box::new(file::DatabaseFileHandler),
197200
static_handler: Box::new(static_handler),
@@ -202,10 +205,13 @@ impl CratesfyiHandler {
202205

203206
impl Handler for CratesfyiHandler {
204207
fn handle(&self, req: &mut Request) -> IronResult<Response> {
205-
// try router first then db/static file handler
208+
// try serving shared rustdoc resources first, then router, then db/static file handler
206209
// return 404 if none of them return Ok
207-
self.router_handler
210+
self.shared_resource_handler
208211
.handle(req)
212+
.or_else(|e| {
213+
self.router_handler.handle(req).or(Err(e))
214+
})
209215
.or_else(|e| {
210216
// if router fails try to serve files from database first
211217
self.database_file_handler.handle(req).or(Err(e))

src/web/rustdoc.rs

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use rustc_serialize::json::{Json, ToJson};
1616
use std::collections::BTreeMap;
1717
use iron::headers::{Expires, HttpDate, CacheControl, CacheDirective};
1818
use time;
19-
19+
use iron::Handler;
20+
use utils;
2021

2122

2223
#[derive(Debug)]
@@ -110,6 +111,10 @@ pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult<Response> {
110111
}
111112

112113

114+
/// Serves documentation generated by rustdoc.
115+
///
116+
/// This includes all HTML files for an individual crate, as well as the `search-index.js`, which is
117+
/// also crate-specific.
113118
pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
114119

115120
let router = extension!(req, Router);
@@ -151,34 +156,13 @@ pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {
151156
return Ok(file.serve());
152157
}
153158

154-
let (mut in_head, mut in_body) = (false, false);
155-
156159
let mut content = RustdocPage::default();
157160

158161
let file_content = ctry!(String::from_utf8(file.content));
159162

160-
for line in file_content.lines() {
161-
162-
if line.starts_with("<head") {
163-
in_head = true;
164-
continue;
165-
} else if line.starts_with("</head") {
166-
in_head = false;
167-
} else if line.starts_with("<body") {
168-
in_body = true;
169-
continue;
170-
} else if line.starts_with("</body") {
171-
in_body = false;
172-
}
173-
174-
if in_head {
175-
content.head.push_str(&line[..]);
176-
content.head.push('\n');
177-
} else if in_body {
178-
content.body.push_str(&line[..]);
179-
content.body.push('\n');
180-
}
181-
}
163+
let (head, body) = ctry!(utils::extract_head_and_body(&file_content));
164+
content.head = head;
165+
content.body = body;
182166

183167
content.full = file_content;
184168
let crate_details = cexpect!(CrateDetails::new(&conn, &name, &version));
@@ -251,3 +235,28 @@ pub fn badge_handler(req: &mut Request) -> IronResult<Response> {
251235
CacheDirective::MustRevalidate]));
252236
Ok(resp)
253237
}
238+
239+
/// Serves shared web resources used by rustdoc-generated documentation.
240+
///
241+
/// This includes common `css` and `js` files that only change when the compiler is updated, but are
242+
/// otherwise the same for all crates documented with that compiler. Those have a custom handler to
243+
/// deduplicate them and save space.
244+
pub struct SharedResourceHandler;
245+
246+
impl Handler for SharedResourceHandler {
247+
fn handle(&self, req: &mut Request) -> IronResult<Response> {
248+
let path = req.url.path();
249+
let filename = path.last().unwrap(); // unwrap is fine: vector is non-empty
250+
let suffix = filename.split('.').last().unwrap(); // unwrap is fine: split always works
251+
if ["js", "css", "woff", "svg"].contains(&suffix) {
252+
let conn = extension!(req, Pool);
253+
254+
if let Some(file) = File::from_path(conn, filename) {
255+
return Ok(file.serve());
256+
}
257+
}
258+
259+
// Just always return a 404 here - the main handler will then try the other handlers
260+
Err(IronError::new(Nope::ResourceNotFound, status::NotFound))
261+
}
262+
}

templates/rustdoc.hbs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,17 @@
1-
{{{content.rustdoc_full}}}
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
{{{content.rustdoc_head}}}
5+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pure/0.6.0/menus-min.css" type="text/css" media="all" />
6+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pure/0.6.0/grids-min.css" type="text/css" media="all" />
7+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" type="text/css" media="all" />
8+
<link rel="stylesheet" href="/style.css?{{cratesfyi_version_safe}}" type="text/css" media="all" />
9+
<link rel="search" href="/opensearch.xml" type="application/opensearchdescription+xml" title="Docs.rs">
10+
</head>
11+
<body>
12+
{{> navigation_rustdoc}}
13+
<div class="rustdoc container-rustdoc">
14+
{{{content.rustdoc_body}}}
15+
</div>
16+
</body>
17+
</html>

0 commit comments

Comments
 (0)