From ee6df13f0c25ce567b12459d1f34216334832920 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 7 Mar 2016 22:32:37 -0800 Subject: rustbuild: Move rustbook to a `src/tools` directory We've actually got quite a few tools that are compiled as part of our build, let's start housing them all in a `tools` directory. --- src/tools/rustbook/Cargo.lock | 4 + src/tools/rustbook/Cargo.toml | 8 ++ src/tools/rustbook/book.rs | 171 +++++++++++++++++++++++++ src/tools/rustbook/build.rs | 225 +++++++++++++++++++++++++++++++++ src/tools/rustbook/error.rs | 36 ++++++ src/tools/rustbook/help.rs | 46 +++++++ src/tools/rustbook/main.rs | 72 +++++++++++ src/tools/rustbook/serve.rs | 36 ++++++ src/tools/rustbook/static/rustbook.css | 157 +++++++++++++++++++++++ src/tools/rustbook/static/rustbook.js | 78 ++++++++++++ src/tools/rustbook/subcommand.rs | 44 +++++++ src/tools/rustbook/term.rs | 34 +++++ src/tools/rustbook/test.rs | 75 +++++++++++ 13 files changed, 986 insertions(+) create mode 100644 src/tools/rustbook/Cargo.lock create mode 100644 src/tools/rustbook/Cargo.toml create mode 100644 src/tools/rustbook/book.rs create mode 100644 src/tools/rustbook/build.rs create mode 100644 src/tools/rustbook/error.rs create mode 100644 src/tools/rustbook/help.rs create mode 100644 src/tools/rustbook/main.rs create mode 100644 src/tools/rustbook/serve.rs create mode 100644 src/tools/rustbook/static/rustbook.css create mode 100644 src/tools/rustbook/static/rustbook.js create mode 100644 src/tools/rustbook/subcommand.rs create mode 100644 src/tools/rustbook/term.rs create mode 100644 src/tools/rustbook/test.rs (limited to 'src/tools/rustbook') 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 or the MIT license +// , 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, +} + +pub struct Book { + pub chapters: Vec, +} + +/// 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> { + fn collapse(stack: &mut Vec, + top_items: &mut Vec, + 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::()); + 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::() / 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 or the MIT license +// , 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> { + 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, "
  • {} {}", + 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, "
      ")); + let _ = walk_items(&item.children[..], section, current_page, out); + try!(writeln!(out, "
    ")); + } + try!(writeln!(out, "
  • ")); + + Ok(()) + } + + try!(writeln!(out, "
    ")); + try!(writeln!(out, "
      ")); + try!(walk_items(&book.chapters[..], "", ¤t_page, out)); + try!(writeln!(out, "
    ")); + try!(writeln!(out, "
    ")); + + 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#" + "#)); + let _ = write_toc(book, &item, &mut buffer); + try!(writeln!(&mut buffer, "
    ")); + try!(writeln!(&mut buffer, "
    ")); + } + + // 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, "")); + try!(writeln!(&mut buffer, "")); + try!(writeln!(&mut buffer, "
    ")); + } + + 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 or the MIT license +// , 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; +pub type CliResult = Result; + +pub type CommandError = Box; +pub type CommandResult = Result; + +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 or the MIT license +// , 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> { + 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 []"); + println!(""); + println!("The 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 or the MIT license +// , 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 or the MIT license +// , 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> { + 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 or the MIT license + * , 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 or the MIT license +// , 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 or the MIT license +// , 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> { + let cmds: [fn(&str) -> Option>; 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 or the MIT license +// , 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 +} + +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 or the MIT license +// , 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> { + 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 + } +} -- cgit 1.4.1-3-g733a5