diff options
| author | bors <bors@rust-lang.org> | 2016-03-11 04:38:04 -0800 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2016-03-11 04:38:04 -0800 |
| commit | aeb85a953322df7773095186e9762f3fe73375e2 (patch) | |
| tree | 81ea15e110932e2a990351a9bea602599c23cb6c /src/tools/rustbook | |
| parent | 40c85cd8aec5de06140252610ad4bdc352324e2c (diff) | |
| parent | 3e6fed3a7a3f783bf967f6c73455743848f31167 (diff) | |
Auto merge of #32133 - alexcrichton:linkchecker, r=brson
Add a link validator to rustbuild This commit was originally targeted at just adding a link checking script to the rustbuild system. This ended up snowballing a bit to extend rustbuild to be amenable to various tools we have as part of the build system in general. There's a new `src/tools` directory which has a number of scripts/programs that are purely intended to be used as part of the build system and CI of this repository. This is currently inhabited by rustbook, the error index generator, and a new linkchecker script added as part of this PR. I suspect that more tools like compiletest, tidy scripts, snapshot scripts, etc will migrate their way into this directory over time. The commit which adds the error index generator shows the steps necessary to add new tools to the build system, namely: 1. New steps are defined for building the tool and running the tool 2. The dependencies are configured 3. The steps are implemented In terms of the link checker, these commits do a few things: * A new `src/tools/linkchecker` script is added. This will read an entire documentation tree looking for broken relative links (HTTP links aren't followed yet). * A large number of broken links throughout the documentation were fixed. Many of these were just broken when viewed from core as opposed to std, but were easily fixed. * A few rustdoc bugs here and there were fixed
Diffstat (limited to 'src/tools/rustbook')
| -rw-r--r-- | src/tools/rustbook/Cargo.lock | 4 | ||||
| -rw-r--r-- | src/tools/rustbook/Cargo.toml | 8 | ||||
| -rw-r--r-- | src/tools/rustbook/book.rs | 171 | ||||
| -rw-r--r-- | src/tools/rustbook/build.rs | 225 | ||||
| -rw-r--r-- | src/tools/rustbook/error.rs | 36 | ||||
| -rw-r--r-- | src/tools/rustbook/help.rs | 46 | ||||
| -rw-r--r-- | src/tools/rustbook/main.rs | 72 | ||||
| -rw-r--r-- | src/tools/rustbook/serve.rs | 36 | ||||
| -rw-r--r-- | src/tools/rustbook/static/rustbook.css | 157 | ||||
| -rw-r--r-- | src/tools/rustbook/static/rustbook.js | 78 | ||||
| -rw-r--r-- | src/tools/rustbook/subcommand.rs | 44 | ||||
| -rw-r--r-- | src/tools/rustbook/term.rs | 34 | ||||
| -rw-r--r-- | src/tools/rustbook/test.rs | 75 |
13 files changed, 986 insertions, 0 deletions
diff --git a/src/tools/rustbook/Cargo.lock b/src/tools/rustbook/Cargo.lock new file mode 100644 index 00000000000..e541ce4b2b8 --- /dev/null +++ b/src/tools/rustbook/Cargo.lock @@ -0,0 +1,4 @@ +[root] +name = "rustbook" +version = "0.0.0" + diff --git a/src/tools/rustbook/Cargo.toml b/src/tools/rustbook/Cargo.toml new file mode 100644 index 00000000000..956392ca540 --- /dev/null +++ b/src/tools/rustbook/Cargo.toml @@ -0,0 +1,8 @@ +[package] +authors = ["The Rust Project Developers"] +name = "rustbook" +version = "0.0.0" + +[[bin]] +name = "rustbook" +path = "main.rs" diff --git a/src/tools/rustbook/book.rs b/src/tools/rustbook/book.rs new file mode 100644 index 00000000000..36a37dba1fa --- /dev/null +++ b/src/tools/rustbook/book.rs @@ -0,0 +1,171 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Basic data structures for representing a book. + +use std::io::prelude::*; +use std::io::BufReader; +use std::iter; +use std::path::{Path, PathBuf}; + +pub struct BookItem { + pub title: String, + pub path: PathBuf, + pub path_to_root: PathBuf, + pub children: Vec<BookItem>, +} + +pub struct Book { + pub chapters: Vec<BookItem>, +} + +/// A depth-first iterator over a book. +pub struct BookItems<'a> { + cur_items: &'a [BookItem], + cur_idx: usize, + stack: Vec<(&'a [BookItem], usize)>, +} + +impl<'a> Iterator for BookItems<'a> { + type Item = (String, &'a BookItem); + + fn next(&mut self) -> Option<(String, &'a BookItem)> { + loop { + if self.cur_idx >= self.cur_items.len() { + match self.stack.pop() { + None => return None, + Some((parent_items, parent_idx)) => { + self.cur_items = parent_items; + self.cur_idx = parent_idx + 1; + } + } + } else { + let cur = self.cur_items.get(self.cur_idx).unwrap(); + + let mut section = "".to_string(); + for &(_, idx) in &self.stack { + section.push_str(&(idx + 1).to_string()[..]); + section.push('.'); + } + section.push_str(&(self.cur_idx + 1).to_string()[..]); + section.push('.'); + + self.stack.push((self.cur_items, self.cur_idx)); + self.cur_items = &cur.children[..]; + self.cur_idx = 0; + return Some((section, cur)) + } + } + } +} + +impl Book { + pub fn iter(&self) -> BookItems { + BookItems { + cur_items: &self.chapters[..], + cur_idx: 0, + stack: Vec::new(), + } + } +} + +/// Construct a book by parsing a summary (markdown table of contents). +pub fn parse_summary(input: &mut Read, src: &Path) -> Result<Book, Vec<String>> { + fn collapse(stack: &mut Vec<BookItem>, + top_items: &mut Vec<BookItem>, + to_level: usize) { + loop { + if stack.len() < to_level { return } + if stack.len() == 1 { + top_items.push(stack.pop().unwrap()); + return; + } + + let tip = stack.pop().unwrap(); + let last = stack.len() - 1; + stack[last].children.push(tip); + } + } + + let mut top_items = vec!(); + let mut stack = vec!(); + let mut errors = vec!(); + + // always include the introduction + top_items.push(BookItem { + title: "Introduction".to_string(), + path: PathBuf::from("README.md"), + path_to_root: PathBuf::from(""), + children: vec!(), + }); + + for line_result in BufReader::new(input).lines() { + let line = match line_result { + Ok(line) => line, + Err(err) => { + errors.push(err.to_string()); + return Err(errors); + } + }; + + let star_idx = match line.find("*") { Some(i) => i, None => continue }; + + let start_bracket = star_idx + line[star_idx..].find("[").unwrap(); + let end_bracket = start_bracket + line[start_bracket..].find("](").unwrap(); + let start_paren = end_bracket + 1; + let end_paren = start_paren + line[start_paren..].find(")").unwrap(); + + let given_path = &line[start_paren + 1 .. end_paren]; + let title = line[start_bracket + 1..end_bracket].to_string(); + let indent = &line[..star_idx]; + + let path_from_root = match src.join(given_path).strip_prefix(src) { + Ok(p) => p.to_path_buf(), + Err(..) => { + errors.push(format!("paths in SUMMARY.md must be relative, \ + but path '{}' for section '{}' is not.", + given_path, title)); + PathBuf::new() + } + }; + let path_to_root = PathBuf::from(&iter::repeat("../") + .take(path_from_root.components().count() - 1) + .collect::<String>()); + let item = BookItem { + title: title, + path: path_from_root, + path_to_root: path_to_root, + children: vec!(), + }; + let level = indent.chars().map(|c| -> usize { + match c { + ' ' => 1, + '\t' => 4, + _ => unreachable!() + } + }).sum::<usize>() / 4 + 1; + + if level > stack.len() + 1 { + errors.push(format!("section '{}' is indented too deeply; \ + found {}, expected {} or less", + item.title, level, stack.len() + 1)); + } else if level <= stack.len() { + collapse(&mut stack, &mut top_items, level); + } + stack.push(item) + } + + if errors.is_empty() { + collapse(&mut stack, &mut top_items, 1); + Ok(Book { chapters: top_items }) + } else { + Err(errors) + } +} diff --git a/src/tools/rustbook/build.rs b/src/tools/rustbook/build.rs new file mode 100644 index 00000000000..70ed98519f9 --- /dev/null +++ b/src/tools/rustbook/build.rs @@ -0,0 +1,225 @@ +// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation of the `build` subcommand, used to compile a book. + +use std::env; +use std::fs::{self, File}; +use std::io::prelude::*; +use std::io::{self, BufWriter}; +use std::path::{Path, PathBuf}; +use rustc_back::tempdir::TempDir; + +use subcommand::Subcommand; +use term::Term; +use error::{err, CliResult, CommandResult}; +use book; +use book::{Book, BookItem}; + +use rustdoc; + +struct Build; + +pub fn parse_cmd(name: &str) -> Option<Box<Subcommand>> { + if name == "build" { + Some(Box::new(Build)) + } else { + None + } +} + +fn write_toc(book: &Book, current_page: &BookItem, out: &mut Write) -> io::Result<()> { + fn walk_items(items: &[BookItem], + section: &str, + current_page: &BookItem, + out: &mut Write) -> io::Result<()> { + for (i, item) in items.iter().enumerate() { + try!(walk_item(item, &format!("{}{}.", section, i + 1)[..], current_page, out)); + } + Ok(()) + } + fn walk_item(item: &BookItem, + section: &str, + current_page: &BookItem, + out: &mut Write) -> io::Result<()> { + let class_string = if item.path == current_page.path { + "class='active'" + } else { + "" + }; + + try!(writeln!(out, "<li><a {} href='{}'><b>{}</b> {}</a>", + class_string, + current_page.path_to_root.join(&item.path).with_extension("html").display(), + section, + item.title)); + if !item.children.is_empty() { + try!(writeln!(out, "<ul class='section'>")); + let _ = walk_items(&item.children[..], section, current_page, out); + try!(writeln!(out, "</ul>")); + } + try!(writeln!(out, "</li>")); + + Ok(()) + } + + try!(writeln!(out, "<div id='toc' class='mobile-hidden'>")); + try!(writeln!(out, "<ul class='chapter'>")); + try!(walk_items(&book.chapters[..], "", ¤t_page, out)); + try!(writeln!(out, "</ul>")); + try!(writeln!(out, "</div>")); + + Ok(()) +} + +fn render(book: &Book, tgt: &Path) -> CliResult<()> { + let tmp = try!(TempDir::new("rustbook")); + + for (_section, item) in book.iter() { + let out_path = match item.path.parent() { + Some(p) => tgt.join(p), + None => tgt.to_path_buf(), + }; + + let src; + if env::args().len() < 3 { + src = env::current_dir().unwrap().clone(); + } else { + src = PathBuf::from(&env::args().nth(2).unwrap()); + } + // preprocess the markdown, rerouting markdown references to html + // references + let mut markdown_data = String::new(); + try!(File::open(&src.join(&item.path)).and_then(|mut f| { + f.read_to_string(&mut markdown_data) + })); + let preprocessed_path = tmp.path().join(item.path.file_name().unwrap()); + { + let urls = markdown_data.replace(".md)", ".html)"); + try!(File::create(&preprocessed_path).and_then(|mut f| { + f.write_all(urls.as_bytes()) + })); + } + + // write the prelude to a temporary HTML file for rustdoc inclusion + let prelude = tmp.path().join("prelude.html"); + { + let mut buffer = BufWriter::new(try!(File::create(&prelude))); + try!(writeln!(&mut buffer, r#" + <div id="nav"> + <button id="toggle-nav"> + <span class="sr-only">Toggle navigation</span> + <span class="bar"></span> + <span class="bar"></span> + <span class="bar"></span> + </button> + </div>"#)); + let _ = write_toc(book, &item, &mut buffer); + try!(writeln!(&mut buffer, "<div id='page-wrapper'>")); + try!(writeln!(&mut buffer, "<div id='page'>")); + } + + // write the postlude to a temporary HTML file for rustdoc inclusion + let postlude = tmp.path().join("postlude.html"); + { + let mut buffer = BufWriter::new(try!(File::create(&postlude))); + try!(writeln!(&mut buffer, "<script src='rustbook.js'></script>")); + try!(writeln!(&mut buffer, "<script src='playpen.js'></script>")); + try!(writeln!(&mut buffer, "</div></div>")); + } + + try!(fs::create_dir_all(&out_path)); + + let rustdoc_args: &[String] = &[ + "".to_string(), + preprocessed_path.display().to_string(), + format!("-o{}", out_path.display()), + format!("--html-before-content={}", prelude.display()), + format!("--html-after-content={}", postlude.display()), + format!("--markdown-playground-url=https://play.rust-lang.org"), + format!("--markdown-css={}", item.path_to_root.join("rustbook.css").display()), + "--markdown-no-toc".to_string(), + ]; + let output_result = rustdoc::main_args(rustdoc_args); + if output_result != 0 { + let message = format!("Could not execute `rustdoc` with {:?}: {}", + rustdoc_args, output_result); + return Err(err(&message)); + } + } + + // create index.html from the root README + try!(fs::copy(&tgt.join("README.html"), &tgt.join("index.html"))); + + // Copy js for playpen + let mut playpen = try!(File::create(tgt.join("playpen.js"))); + let js = include_bytes!("../../librustdoc/html/static/playpen.js"); + try!(playpen.write_all(js)); + Ok(()) +} + +impl Subcommand for Build { + fn parse_args(&mut self, _: &[String]) -> CliResult<()> { + Ok(()) + } + fn usage(&self) {} + fn execute(&mut self, term: &mut Term) -> CommandResult<()> { + let cwd = env::current_dir().unwrap(); + let src; + let tgt; + + if env::args().len() < 3 { + src = cwd.clone(); + } else { + src = PathBuf::from(&env::args().nth(2).unwrap()); + } + + if env::args().len() < 4 { + tgt = cwd.join("_book"); + } else { + tgt = PathBuf::from(&env::args().nth(3).unwrap()); + } + + // `_book` directory may already exist from previous runs. Check and + // delete it if it exists. + for entry in try!(fs::read_dir(&cwd)) { + let path = try!(entry).path(); + if path == tgt { try!(fs::remove_dir_all(&tgt)) } + } + try!(fs::create_dir(&tgt)); + + // Copy static files + let css = include_bytes!("static/rustbook.css"); + let js = include_bytes!("static/rustbook.js"); + + let mut css_file = try!(File::create(tgt.join("rustbook.css"))); + try!(css_file.write_all(css)); + + let mut js_file = try!(File::create(tgt.join("rustbook.js"))); + try!(js_file.write_all(js)); + + + let mut summary = try!(File::open(&src.join("SUMMARY.md"))); + match book::parse_summary(&mut summary, &src) { + Ok(book) => { + // execute rustdoc on the whole book + render(&book, &tgt) + } + Err(errors) => { + let n = errors.len(); + for err in errors { + term.err(&format!("error: {}", err)[..]); + } + + Err(err(&format!("{} errors occurred", n))) + } + } + } +} diff --git a/src/tools/rustbook/error.rs b/src/tools/rustbook/error.rs new file mode 100644 index 00000000000..e896dee2791 --- /dev/null +++ b/src/tools/rustbook/error.rs @@ -0,0 +1,36 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Error handling utilities. WIP. + +use std::error::Error; +use std::fmt; + +pub type CliError = Box<Error + 'static>; +pub type CliResult<T> = Result<T, CliError>; + +pub type CommandError = Box<Error + 'static>; +pub type CommandResult<T> = Result<T, CommandError>; + +pub fn err(s: &str) -> CliError { + #[derive(Debug)] + struct E(String); + + impl Error for E { + fn description(&self) -> &str { &self.0 } + } + impl fmt::Display for E { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } + } + + Box::new(E(s.to_string())) +} diff --git a/src/tools/rustbook/help.rs b/src/tools/rustbook/help.rs new file mode 100644 index 00000000000..c90c2b93609 --- /dev/null +++ b/src/tools/rustbook/help.rs @@ -0,0 +1,46 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation of the `help` subcommand. Currently just prints basic usage info. + +use subcommand::Subcommand; +use error::CliResult; +use error::CommandResult; +use term::Term; + +struct Help; + +pub fn parse_cmd(name: &str) -> Option<Box<Subcommand>> { + match name { + "help" | "--help" | "-h" | "-?" => Some(Box::new(Help)), + _ => None + } +} + +impl Subcommand for Help { + fn parse_args(&mut self, _: &[String]) -> CliResult<()> { + Ok(()) + } + fn usage(&self) {} + fn execute(&mut self, _: &mut Term) -> CommandResult<()> { + usage(); + Ok(()) + } +} + +pub fn usage() { + println!("Usage: rustbook <command> [<args>]"); + println!(""); + println!("The <command> must be one of:"); + println!(" help Print this message."); + println!(" build Build the book in subdirectory _book"); + println!(" serve --NOT YET IMPLEMENTED--"); + println!(" test --NOT YET IMPLEMENTED--"); +} diff --git a/src/tools/rustbook/main.rs b/src/tools/rustbook/main.rs new file mode 100644 index 00000000000..bd4fc899293 --- /dev/null +++ b/src/tools/rustbook/main.rs @@ -0,0 +1,72 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![deny(warnings)] + +#![feature(iter_arith)] +#![feature(rustc_private)] +#![feature(rustdoc)] + +extern crate rustdoc; +extern crate rustc_back; + +use std::env; +use std::error::Error; +use std::process; +use std::sync::atomic::{AtomicIsize, ATOMIC_ISIZE_INIT, Ordering}; +use subcommand::Subcommand; +use term::Term; + +mod term; +mod error; +mod book; + +mod subcommand; +mod help; +mod build; +mod serve; +mod test; + +static EXIT_STATUS: AtomicIsize = ATOMIC_ISIZE_INIT; + +pub fn main() { + let mut term = Term::new(); + let cmd: Vec<_> = env::args().collect(); + + if cmd.len() <= 1 { + help::usage() + } else { + match subcommand::parse_name(&cmd[1][..]) { + Some(mut subcmd) => { + match subcmd.parse_args(&cmd[..cmd.len()-1]) { + Ok(_) => { + match subcmd.execute(&mut term) { + Ok(_) => (), + Err(err) => { + term.err(&format!("error: {}", err)); + } + } + } + Err(err) => { + println!("{}", err.description()); + println!(""); + subcmd.usage(); + } + } + } + None => { + println!("Unrecognized command '{}'.", cmd[1]); + println!(""); + help::usage(); + } + } + } + process::exit(EXIT_STATUS.load(Ordering::SeqCst) as i32); +} diff --git a/src/tools/rustbook/serve.rs b/src/tools/rustbook/serve.rs new file mode 100644 index 00000000000..2fa7b7eed7b --- /dev/null +++ b/src/tools/rustbook/serve.rs @@ -0,0 +1,36 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation of the `serve` subcommand. Just a stub for now. + +use subcommand::Subcommand; +use error::CliResult; +use error::CommandResult; +use term::Term; + +struct Serve; + +pub fn parse_cmd(name: &str) -> Option<Box<Subcommand>> { + if name == "serve" { + Some(Box::new(Serve)) + } else { + None + } +} + +impl Subcommand for Serve { + fn parse_args(&mut self, _: &[String]) -> CliResult<()> { + Ok(()) + } + fn usage(&self) {} + fn execute(&mut self, _: &mut Term) -> CommandResult<()> { + Ok(()) + } +} diff --git a/src/tools/rustbook/static/rustbook.css b/src/tools/rustbook/static/rustbook.css new file mode 100644 index 00000000000..ba0151fa2ed --- /dev/null +++ b/src/tools/rustbook/static/rustbook.css @@ -0,0 +1,157 @@ +/** + * Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT + * file at the top-level directory of this distribution and at + * http://rust-lang.org/COPYRIGHT. + * + * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or + * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license + * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your + * option. This file may not be copied, modified, or distributed + * except according to those terms. + */ + +@import url('../rust.css'); + +body { + max-width: none; + font: 16px/1.6 'Source Serif Pro', Georgia, Times, 'Times New Roman', serif; + color: #333; +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Open Sans', 'Fira Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: bold; + color: #333; +} + +@media only screen { + #toc { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 300px; + overflow-y: auto; + border-right: 1px solid #e8e8e8; + padding: 0 15px; + font-size: 14px; + background-color: #fafafa; + -webkit-overflow-scrolling: touch; + } + + #page-wrapper { + position: absolute; + top: 0; + left: 300px; + right: 0; + padding: 0 15px; + -webkit-overflow-scrolling: touch; + } +} + +@media only print { + #toc, #nav { + display: none; + } +} + +@media only screen and (max-width: 1023px) { + #toc { + width: 100%; + top: 40px; + } + + #page-wrapper { + top: 40px; + left: 0; + } + + .mobile-hidden { + display: none; + } +} + +#page { + margin: 0 auto; + max-width: 750px; + padding-bottom: 50px; +} + +.chapter { + list-style: none; + padding-left: 0; + line-height: 30px; +} + +.section { + list-style: none; + padding-left: 20px; + line-height: 40px; +} + +.section li { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.chapter li a { + color: #333; + padding: 5px 0; +} + +.chapter li a.active, +.chapter li a:hover { + color: #008cff; + text-decoration: none; +} + +#toggle-nav { + cursor: pointer; + margin-top: 5px; + width: 30px; + height: 30px; + background-color: #fff; + border: 1px solid #666; + border-radius: 3px; + padding: 3px 3px 0 3px; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +.bar { + display: block; + background-color: #000; + border-radius: 2px; + width: 100%; + height: 2px; + margin: 2px 0 3px; + padding: 0; +} + +pre { + padding: 11px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: #f7f7f7; + border: 0; + border-radius: 3px; +} + +.left { + float: left; +} + +.right { + float: right; +} diff --git a/src/tools/rustbook/static/rustbook.js b/src/tools/rustbook/static/rustbook.js new file mode 100644 index 00000000000..d8ab15260ed --- /dev/null +++ b/src/tools/rustbook/static/rustbook.js @@ -0,0 +1,78 @@ +// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/*jslint browser: true, es5: true */ +/*globals $: true, rootPath: true */ + +document.addEventListener('DOMContentLoaded', function() { + 'use strict'; + + document.getElementById('toggle-nav').onclick = function(e) { + var toc = document.getElementById('toc'); + var pagewrapper = document.getElementById('page-wrapper'); + toggleClass(toc, 'mobile-hidden'); + toggleClass(pagewrapper, 'mobile-hidden'); + }; + + function toggleClass(el, className) { + // from http://youmightnotneedjquery.com/ + if (el.classList) { + el.classList.toggle(className); + } else { + var classes = el.className.split(' '); + var existingIndex = classes.indexOf(className); + + if (existingIndex >= 0) { + classes.splice(existingIndex, 1); + } else { + classes.push(className); + } + + el.className = classes.join(' '); + } + } + + // The below code is used to add prev and next navigation links to the + // bottom of each of the sections. + // It works by extracting the current page based on the url and iterates + // over the menu links until it finds the menu item for the current page. We + // then create a copy of the preceding and following menu links and add the + // correct css class and insert them into the bottom of the page. + var toc = document.getElementById('toc').getElementsByTagName('a'); + var href = document.location.pathname.split('/').pop(); + + if (href === 'index.html' || href === '') { + href = 'README.html'; + } + + for (var i = 0; i < toc.length; i++) { + if (toc[i].attributes.href.value.split('/').pop() === href) { + var nav = document.createElement('p'); + + if (i > 0) { + var prevNode = toc[i-1].cloneNode(true); + prevNode.className = 'left'; + prevNode.setAttribute('rel', 'prev'); + nav.appendChild(prevNode); + } + + if (i < toc.length - 1) { + var nextNode = toc[i+1].cloneNode(true); + nextNode.className = 'right'; + nextNode.setAttribute('rel', 'next'); + nav.appendChild(nextNode); + } + + document.getElementById('page').appendChild(nav); + + break; + } + } +}); diff --git a/src/tools/rustbook/subcommand.rs b/src/tools/rustbook/subcommand.rs new file mode 100644 index 00000000000..a66c2b4f302 --- /dev/null +++ b/src/tools/rustbook/subcommand.rs @@ -0,0 +1,44 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Common API for all rustbook subcommands. + +use error::CliResult; +use error::CommandResult; +use term::Term; + +use help; +use build; +use serve; +use test; + +pub trait Subcommand { + /// Mutate the subcommand by parsing its arguments. + /// + /// Returns `Err` on a parsing error. + fn parse_args(&mut self, args: &[String]) -> CliResult<()>; + /// Print the CLI usage information. + fn usage(&self); + /// Actually execute the subcommand. + fn execute(&mut self, term: &mut Term) -> CommandResult<()>; +} + +/// Create a Subcommand object based on its name. +pub fn parse_name(name: &str) -> Option<Box<Subcommand>> { + let cmds: [fn(&str) -> Option<Box<Subcommand>>; 4] = [help::parse_cmd, + build::parse_cmd, + serve::parse_cmd, + test::parse_cmd]; + for parser in &cmds { + let parsed = (*parser)(name); + if parsed.is_some() { return parsed } + } + None +} diff --git a/src/tools/rustbook/term.rs b/src/tools/rustbook/term.rs new file mode 100644 index 00000000000..cdd25e67c8f --- /dev/null +++ b/src/tools/rustbook/term.rs @@ -0,0 +1,34 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! An abstraction of the terminal. Eventually, provide color and +//! verbosity support. For now, just a wrapper around stdout/stderr. + +use std::io; +use std::io::prelude::*; +use std::sync::atomic::Ordering; + +pub struct Term { + err: Box<Write + 'static> +} + +impl Term { + pub fn new() -> Term { + Term { + err: Box::new(io::stderr()) + } + } + + pub fn err(&mut self, msg: &str) { + // swallow any errors + let _ = writeln!(&mut self.err, "{}", msg); + ::EXIT_STATUS.store(101, Ordering::SeqCst); + } +} diff --git a/src/tools/rustbook/test.rs b/src/tools/rustbook/test.rs new file mode 100644 index 00000000000..72df0768e7b --- /dev/null +++ b/src/tools/rustbook/test.rs @@ -0,0 +1,75 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation of the `test` subcommand. Just a stub for now. + +use subcommand::Subcommand; +use error::{err, CliResult, CommandResult}; +use term::Term; +use book; + +use std::fs::File; +use std::env; +use std::process::Command; + +struct Test; + +pub fn parse_cmd(name: &str) -> Option<Box<Subcommand>> { + if name == "test" { + Some(Box::new(Test)) + } else { + None + } +} + +impl Subcommand for Test { + fn parse_args(&mut self, _: &[String]) -> CliResult<()> { + Ok(()) + } + fn usage(&self) {} + fn execute(&mut self, term: &mut Term) -> CommandResult<()> { + let cwd = env::current_dir().unwrap(); + let src = cwd.clone(); + + let mut summary = try!(File::open(&src.join("SUMMARY.md"))); + match book::parse_summary(&mut summary, &src) { + Ok(book) => { + for (_, item) in book.iter() { + let output_result = Command::new("rustdoc") + .arg(&item.path) + .arg("--test") + .output(); + match output_result { + Ok(output) => { + if !output.status.success() { + term.err(&format!("{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr))); + return Err(err("some tests failed")); + } + + } + Err(e) => { + let message = format!("could not execute `rustdoc`: {}", e); + return Err(err(&message)) + } + } + } + } + Err(errors) => { + for err in errors { + term.err(&err[..]); + } + return Err(err("there was an error")) + } + } + Ok(()) // lol + } +} |
