Re-implement rustbook in terms of mdbook

mdbook has a lot of optional dependencies that we don't want, so instead
of using it directly, we re-build rustbook to use mdbook as a library.
For convenience' sake, we keep the same CLI interface as mdbook; the
only difference is that it only accepts build and test subcommands,
rather than the full range.
This commit is contained in:
Steve Klabnik 2017-02-07 18:13:57 -05:00
parent 717ac960b5
commit a076961fd0
19 changed files with 311 additions and 972 deletions

216
src/Cargo.lock generated
View File

@ -6,6 +6,14 @@ dependencies = [
"libc 0.0.0",
]
[[package]]
name = "aho-corasick"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "alloc"
version = "0.0.0"
@ -31,10 +39,25 @@ dependencies = [
"libc 0.0.0",
]
[[package]]
name = "ansi_term"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "arena"
version = "0.0.0"
[[package]]
name = "bitflags"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bootstrap"
version = "0.0.0"
@ -69,6 +92,21 @@ dependencies = [
name = "cargotest"
version = "0.1.0"
[[package]]
name = "clap"
version = "2.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cmake"
version = "0.1.18"
@ -114,6 +152,7 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -158,6 +197,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "graphviz"
version = "0.0.0"
[[package]]
name = "handlebars"
version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"pest 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-error 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.0.0"
@ -183,6 +249,27 @@ name = "log"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "mdbook"
version = "0.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clap 2.19.3 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"handlebars 0.20.5 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num_cpus"
version = "0.2.13"
@ -209,6 +296,11 @@ dependencies = [
"unwind 0.0.0",
]
[[package]]
name = "pest"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc_macro"
version = "0.0.0"
@ -225,6 +317,15 @@ dependencies = [
"syntax_pos 0.0.0",
]
[[package]]
name = "pulldown-cmark"
version = "0.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "qemu-test-client"
version = "0.1.0"
@ -233,6 +334,11 @@ version = "0.1.0"
name = "qemu-test-server"
version = "0.1.0"
[[package]]
name = "quick-error"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand"
version = "0.0.0"
@ -240,9 +346,30 @@ dependencies = [
"core 0.0.0",
]
[[package]]
name = "regex"
version = "0.1.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustbook"
version = "0.0.0"
version = "0.1.0"
dependencies = [
"clap 2.19.3 (registry+https://github.com/rust-lang/crates.io-index)",
"mdbook 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc"
@ -650,6 +777,11 @@ dependencies = [
"core 0.0.0",
]
[[package]]
name = "strsim"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syntax"
version = "0.0.0"
@ -685,6 +817,16 @@ dependencies = [
name = "term"
version = "0.0.0"
[[package]]
name = "term_size"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "test"
version = "0.0.0"
@ -700,6 +842,23 @@ dependencies = [
"test 0.0.0",
]
[[package]]
name = "thread-id"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tidy"
version = "0.1.0"
@ -712,14 +871,69 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-segmentation"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "utf8-ranges"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum clap 2.19.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95b78f3fe0fc94c13c731714363260e04b557a637166f33a4570d3189d642374"
"checksum cmake 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0e5bcf27e097a184c1df4437654ed98df3d7a516e8508a6ba45d8b092bbdf283"
"checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f"
"checksum filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5363ab8e4139b8568a6237db5248646e5a8a2f89bd5ccb02092182b11fd3e922"
"checksum gcc 0.3.43 (registry+https://github.com/rust-lang/crates.io-index)" = "c07c758b972368e703a562686adb39125707cc1ef3399da8c019fc6c2498a75d"
"checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685"
"checksum handlebars 0.20.5 (registry+https://github.com/rust-lang/crates.io-index)" = "07f9c1d28bcfb97143c95ed0667141677b2b5675c7ba3d5b81459ad43b1073bd"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417"
"checksum libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "044d1360593a78f5c8e5e710beccdc24ab71d1f01bc19a29bcacdba22e8475d8"
"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054"
"checksum mdbook 0.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "a629cd0194bbd0340a70db83a94b27cf9881fac8c55e6a2db983addcb7114ee4"
"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
"checksum num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "cee7e88156f3f9e19bdd598f8d6c9db7bf4078f99f8381f43a55b09648d1a6e3"
"checksum pest 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0a6dda33d67c26f0aac90d324ab2eb7239c819fc7b2552fe9faa4fe88441edc8"
"checksum pulldown-cmark 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1058d7bb927ca067656537eec4e02c2b4b70eaaa129664c5b90c111e20326f41"
"checksum quick-error 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0aad603e8d7fb67da22dbdf1f4b826ce8829e406124109e73cf1b2454b93a71c"
"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b"
"checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c"
"checksum term_size 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7f5f3f71b0040cecc71af239414c23fd3c73570f5ff54cf50e03cef637f2a0"
"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
"checksum toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "0590d72182e50e879c4da3b11c6488dae18fccb1ae0c7a3eda18e16795844796"
"checksum unicode-segmentation 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c3bc443ded17b11305ffffe6b37e2076f328a5a8cb6aa877b1b98f77699e98b5"
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
"checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"

View File

@ -47,6 +47,7 @@ pub fn rustbook(build: &Build, target: &str, name: &str) {
build.run(build.tool_cmd(&compiler, "rustbook")
.arg("build")
.arg(&src)
.arg("-d")
.arg(out));
}

View File

@ -0,0 +1,3 @@
# Summary
- [Chapter 1](./chapter_1.md)

View File

@ -0,0 +1 @@
# Chapter 1

View File

@ -0,0 +1,3 @@
# Summary
- [Chapter 1](./chapter_1.md)

View File

@ -0,0 +1 @@
# Chapter 1

View File

@ -1,9 +1,12 @@
[package]
authors = ["The Rust Project Developers"]
name = "rustbook"
version = "0.0.0"
build = false
version = "0.1.0"
license = "MIT/Apache-2.0"
[[bin]]
name = "rustbook"
path = "main.rs"
[dependencies]
clap = "2.19.3"
[dependencies.mdbook]
version = "0.0.14"
default-features = false

View File

@ -1,171 +0,0 @@
// 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)
}
}

View File

@ -1,220 +0,0 @@
// 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() {
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 {
""
};
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() {
writeln!(out, "<ol class='section'>")?;
let _ = walk_items(&item.children[..], section, current_page, out);
writeln!(out, "</ol>")?;
}
writeln!(out, "</li>")?;
Ok(())
}
writeln!(out, "<div id='toc' class='mobile-hidden'>")?;
writeln!(out, "<ol class='chapter'>")?;
walk_items(&book.chapters[..], "", &current_page, out)?;
writeln!(out, "</ol>")?;
writeln!(out, "</div>")?;
Ok(())
}
fn render(book: &Book, tgt: &Path) -> CliResult<()> {
let tmp = 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();
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)");
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(File::create(&prelude)?);
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);
writeln!(&mut buffer, "<div id='page-wrapper'>")?;
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(File::create(&postlude)?);
writeln!(&mut buffer, "<script src='rustbook.js'></script>")?;
writeln!(&mut buffer, "</div></div>")?;
}
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
fs::copy(&tgt.join("README.html"), &tgt.join("index.html"))?;
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 fs::read_dir(&cwd)? {
let path = entry?.path();
if path == tgt { fs::remove_dir_all(&tgt)? }
}
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 = File::create(tgt.join("rustbook.css"))?;
css_file.write_all(css)?;
let mut js_file = File::create(tgt.join("rustbook.js"))?;
js_file.write_all(js)?;
let mut summary = 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)))
}
}
}
}

View File

@ -1,36 +0,0 @@
// 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()))
}

View File

@ -1,46 +0,0 @@
// 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--");
}

View File

@ -1,69 +0,0 @@
// 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(rustc_private)]
#![feature(rustdoc)]
extern crate rustdoc;
extern crate rustc_back;
use std::env;
use std::process;
use std::sync::atomic::{AtomicIsize, ATOMIC_ISIZE_INIT, Ordering};
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);
}

View File

@ -1,36 +0,0 @@
// 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(())
}
}

View File

@ -0,0 +1,79 @@
extern crate mdbook;
#[macro_use]
extern crate clap;
use std::env;
use std::error::Error;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use clap::{App, ArgMatches, SubCommand, AppSettings};
use mdbook::MDBook;
const NAME: &'static str = "rustbook";
fn main() {
// Create a list of valid arguments and sub-commands
let matches = App::new(NAME)
.about("Build a book with mdBook")
.author("Steve Klabnik <steve@steveklabnik.com>")
.version(&*format!("v{}", crate_version!()))
.setting(AppSettings::SubcommandRequired)
.subcommand(SubCommand::with_name("build")
.about("Build the book from the markdown files")
.arg_from_usage("-d, --dest-dir=[dest-dir] 'The output directory for your book{n}(Defaults to ./book when omitted)'")
.arg_from_usage("[dir] 'A directory for your book{n}(Defaults to Current Directory when omitted)'"))
.get_matches();
// Check which subcomamnd the user ran...
let res = match matches.subcommand() {
("build", Some(sub_matches)) => build(sub_matches),
("test", Some(sub_matches)) => test(sub_matches),
(_, _) => unreachable!(),
};
if let Err(e) = res {
writeln!(&mut io::stderr(), "An error occured:\n{}", e).ok();
::std::process::exit(101);
}
}
// Build command implementation
fn build(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let book = MDBook::new(&book_dir).read_config();
let mut book = match args.value_of("dest-dir") {
Some(dest_dir) => book.set_dest(Path::new(dest_dir)),
None => book
};
try!(book.build());
Ok(())
}
fn test(args: &ArgMatches) -> Result<(), Box<Error>> {
let book_dir = get_book_dir(args);
let mut book = MDBook::new(&book_dir).read_config();
try!(book.test());
Ok(())
}
fn get_book_dir(args: &ArgMatches) -> PathBuf {
if let Some(dir) = args.value_of("dir") {
// Check if path is relative from current dir, or absolute...
let p = Path::new(dir);
if p.is_relative() {
env::current_dir().unwrap().join(dir)
} else {
p.to_path_buf()
}
} else {
env::current_dir().unwrap()
}
}

View File

@ -1,157 +0,0 @@
/**
* 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;
}

View File

@ -1,78 +0,0 @@
// 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;
}
}
});

View File

@ -1,44 +0,0 @@
// 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
}

View File

@ -1,34 +0,0 @@
// 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);
}
}

View File

@ -1,75 +0,0 @@
// 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 = 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
}
}