├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── README.md ├── deploy.sh ├── librustdoc └── html │ └── static │ └── playpen.js └── src ├── book.rs ├── build.rs ├── css.rs ├── error.rs ├── help.rs ├── javascript.rs ├── main.rs ├── serve.rs ├── static ├── rustbook.css └── rustbook.js ├── subcommand.rs ├── term.rs └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | after_success: 5 | - cargo doc 6 | - test $TRAVIS_PULL_REQUEST == "false" && test $TRAVIS_BRANCH == "master" && bash deploy.sh 7 | 8 | sudo: false 9 | env: 10 | global: 11 | secure: Hb2DqNatr4MA9kCI+eq2AItWqMvrZ+D/cMXQx+Tz4Ww77SIefDOkiK1kkWE9ub3DGbdl9Kyk6+aZqxmssZLsB1txiHez5K7KR3Z3QV5c1IFpAF08zRCljxo73dAx08VQ1daMkOdfKLXVPeI1VlwWuaN+CwkTbGAo8i7oQJGFZl0= 12 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "rustbook" 3 | version = "0.4.0" 4 | 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "rustbook" 4 | version = "0.4.0" 5 | authors = [ "Steve Klabnik ", 6 | "aturon@mozilla.com" ] 7 | 8 | description = "Create multi-chapter documentation with Rustdoc." 9 | license = "Apache-2.0" 10 | repository = "https://github.com/steveklabnik/rustbook" 11 | documentation = "http://steveklabnik.github.io/rustbook/rustbook/" 12 | 13 | [[bin]] 14 | name="rustbook" 15 | test=false 16 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rustbook 2 | 3 | Build multi-page documentation with Rustdoc. 4 | 5 | Please note this is a mirror of https://github.com/rust-lang/rust/tree/master/src/tools/rustbook , so issues should be filed on the language tracker. 6 | 7 | [![Build Status](https://travis-ci.org/steveklabnik/rustbook.svg?branch=master)](https://travis-ci.org/steveklabnik/rustbook) 8 | 9 | - [Documentation](http://steveklabnik.github.io/rustbook/rustbook/) 10 | 11 | ## Acquiring and building 12 | 13 | ``` 14 | git clone https://github.com/steveklabnik/rustbook.git 15 | cd rustbook 16 | cargo build 17 | ``` 18 | 19 | ## Usage 20 | 21 | The `rustbook` tool builds a book from a number of separate markdown files. The 22 | contents of the book are determined by a `SUMMARY.md` file like: 23 | 24 | ```markdown 25 | # Summary 26 | 27 | * [Why to use WhizBang](why/README.md) 28 | * [First reason](why/first.md) 29 | * [Second reason](why/second.md) 30 | * [How to use WhizBang](how/README.md) 31 | * [Installing](how/installing.md) 32 | * [Usage](how/usage.md) 33 | ``` 34 | 35 | The setup is intended to make it easy to browse a book directly on GitHub: 36 | 37 | * By convention, each chapter/section with children is placed in its 38 | own subdirectory, with a `README.md` serving as the top level of the 39 | chapter/section. 40 | 41 | * Books automatically include an `Introduction` section pointing to the 42 | `README.md` in the root directory. 43 | 44 | To build a book, run `rustbook build` in the book's root directory, 45 | which should contain a `SUMMARY.md` and `README.md` as just described. 46 | Currently, the output is always placed in a `_book` subdirectory. 47 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit -o nounset 4 | 5 | rev=$(git rev-parse --short HEAD) 6 | 7 | cd target/doc 8 | 9 | git init 10 | git config user.name "Steve Klabnik" 11 | git config user.email "steve@steveklabnik.com" 12 | 13 | git remote add upstream "https://$GH_TOKEN@github.com/steveklabnik/rustbook.git" 14 | git fetch upstream 15 | git reset upstream/gh-pages 16 | 17 | touch . 18 | 19 | git add -A . 20 | git commit -m "rebuild pages at ${rev}" 21 | git push -q upstream HEAD:gh-pages 22 | -------------------------------------------------------------------------------- /librustdoc/html/static/playpen.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | /*jslint browser: true, es5: true */ 12 | /*globals $: true, rootPath: true */ 13 | 14 | document.addEventListener('DOMContentLoaded', function() { 15 | 'use strict'; 16 | 17 | if (!window.playgroundUrl) { 18 | return; 19 | } 20 | 21 | var featureRegexp = new RegExp('^\s*#!\\[feature\\(\.*?\\)\\]'); 22 | var elements = document.querySelectorAll('pre.rust-example-rendered'); 23 | 24 | Array.prototype.forEach.call(elements, function(el) { 25 | el.onmouseover = function(e) { 26 | if (el.contains(e.relatedTarget)) { 27 | return; 28 | } 29 | 30 | var a = document.createElement('a'); 31 | a.setAttribute('class', 'test-arrow'); 32 | a.textContent = 'Run'; 33 | 34 | var code = el.previousElementSibling.textContent; 35 | 36 | var channel = ''; 37 | if (featureRegexp.test(code)) { 38 | channel = '&version=nightly'; 39 | } 40 | 41 | a.setAttribute('href', window.playgroundUrl + '?code=' + 42 | encodeURIComponent(code) + channel); 43 | a.setAttribute('target', '_blank'); 44 | 45 | el.appendChild(a); 46 | }; 47 | 48 | el.onmouseout = function(e) { 49 | if (el.contains(e.relatedTarget)) { 50 | return; 51 | } 52 | 53 | el.removeChild(el.querySelectorAll('a.test-arrow')[0]); 54 | }; 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/book.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Basic data structures for representing a book. 12 | 13 | use std::io::prelude::*; 14 | use std::io::BufReader; 15 | use std::iter; 16 | use std::path::{Path, PathBuf}; 17 | 18 | pub struct BookItem { 19 | pub title: String, 20 | pub path: PathBuf, 21 | pub path_to_root: PathBuf, 22 | pub children: Vec, 23 | } 24 | 25 | pub struct Book { 26 | pub chapters: Vec, 27 | } 28 | 29 | /// A depth-first iterator over a book. 30 | pub struct BookItems<'a> { 31 | cur_items: &'a [BookItem], 32 | cur_idx: usize, 33 | stack: Vec<(&'a [BookItem], usize)>, 34 | } 35 | 36 | impl<'a> Iterator for BookItems<'a> { 37 | type Item = (String, &'a BookItem); 38 | 39 | fn next(&mut self) -> Option<(String, &'a BookItem)> { 40 | loop { 41 | if self.cur_idx >= self.cur_items.len() { 42 | match self.stack.pop() { 43 | None => return None, 44 | Some((parent_items, parent_idx)) => { 45 | self.cur_items = parent_items; 46 | self.cur_idx = parent_idx + 1; 47 | } 48 | } 49 | } else { 50 | let cur = self.cur_items.get(self.cur_idx).unwrap(); 51 | 52 | let mut section = "".to_string(); 53 | for &(_, idx) in &self.stack { 54 | section.push_str(&(idx + 1).to_string()[..]); 55 | section.push('.'); 56 | } 57 | section.push_str(&(self.cur_idx + 1).to_string()[..]); 58 | section.push('.'); 59 | 60 | self.stack.push((self.cur_items, self.cur_idx)); 61 | self.cur_items = &cur.children[..]; 62 | self.cur_idx = 0; 63 | return Some((section, cur)) 64 | } 65 | } 66 | } 67 | } 68 | 69 | impl Book { 70 | pub fn iter(&self) -> BookItems { 71 | BookItems { 72 | cur_items: &self.chapters[..], 73 | cur_idx: 0, 74 | stack: Vec::new(), 75 | } 76 | } 77 | } 78 | 79 | /// Construct a book by parsing a summary (markdown table of contents). 80 | pub fn parse_summary(input: &mut Read, src: &Path) -> Result> { 81 | fn collapse(stack: &mut Vec, 82 | top_items: &mut Vec, 83 | to_level: usize) { 84 | loop { 85 | if stack.len() < to_level { return } 86 | if stack.len() == 1 { 87 | top_items.push(stack.pop().unwrap()); 88 | return; 89 | } 90 | 91 | let tip = stack.pop().unwrap(); 92 | let last = stack.len() - 1; 93 | stack[last].children.push(tip); 94 | } 95 | } 96 | 97 | let mut top_items = vec![]; 98 | let mut stack = vec![]; 99 | let mut errors = vec![]; 100 | 101 | // always include the introduction 102 | top_items.push(BookItem { 103 | title: "Introduction".to_string(), 104 | path: PathBuf::from("README.md"), 105 | path_to_root: PathBuf::from(""), 106 | children: vec![], 107 | }); 108 | 109 | for line_result in BufReader::new(input).lines() { 110 | let line = match line_result { 111 | Ok(line) => line, 112 | Err(err) => { 113 | errors.push(err.to_string()); 114 | return Err(errors); 115 | } 116 | }; 117 | 118 | let star_idx = match line.find("*") { Some(i) => i, None => continue }; 119 | 120 | let start_bracket = star_idx + line[star_idx..].find("[").unwrap(); 121 | let end_bracket = start_bracket + line[start_bracket..].find("](").unwrap(); 122 | let start_paren = end_bracket + 1; 123 | let end_paren = start_paren + line[start_paren..].find(")").unwrap(); 124 | 125 | let given_path = &line[start_paren + 1 .. end_paren]; 126 | let title = line[start_bracket + 1..end_bracket].to_string(); 127 | let indent = &line[..star_idx]; 128 | 129 | let path_from_root = match src.join(given_path).strip_prefix(src) { 130 | Ok(p) => p.to_path_buf(), 131 | Err(..) => { 132 | errors.push(format!("paths in SUMMARY.md must be relative, \ 133 | but path '{}' for section '{}' is not.", 134 | given_path, title)); 135 | PathBuf::new() 136 | } 137 | }; 138 | let path_to_root = PathBuf::from(&iter::repeat("../") 139 | .take(path_from_root.components().count() - 1) 140 | .collect::()); 141 | let item = BookItem { 142 | title: title, 143 | path: path_from_root, 144 | path_to_root: path_to_root, 145 | children: vec![], 146 | }; 147 | let level = indent.chars().map(|c| -> usize { 148 | match c { 149 | ' ' => 1, 150 | '\t' => 4, 151 | _ => unreachable!() 152 | } 153 | }).sum::() / 4 + 1; 154 | 155 | if level > stack.len() + 1 { 156 | errors.push(format!("section '{}' is indented too deeply; \ 157 | found {}, expected {} or less", 158 | item.title, level, stack.len() + 1)); 159 | } else if level <= stack.len() { 160 | collapse(&mut stack, &mut top_items, level); 161 | } 162 | stack.push(item) 163 | } 164 | 165 | if errors.is_empty() { 166 | collapse(&mut stack, &mut top_items, 1); 167 | Ok(Book { chapters: top_items }) 168 | } else { 169 | Err(errors) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Implementation of the `build` subcommand, used to compile a book. 12 | 13 | use std::env; 14 | use std::fs::{self, File}; 15 | use std::io::prelude::*; 16 | use std::io::{self, BufWriter}; 17 | use std::path::{Path, PathBuf}; 18 | use rustc_back::tempdir::TempDir; 19 | 20 | use subcommand::Subcommand; 21 | use term::Term; 22 | use error::{err, CliResult, CommandResult}; 23 | use book; 24 | use book::{Book, BookItem}; 25 | 26 | use rustdoc; 27 | 28 | struct Build; 29 | 30 | pub fn parse_cmd(name: &str) -> Option> { 31 | if name == "build" { 32 | Some(Box::new(Build)) 33 | } else { 34 | None 35 | } 36 | } 37 | 38 | fn write_toc(book: &Book, current_page: &BookItem, out: &mut Write) -> io::Result<()> { 39 | fn walk_items(items: &[BookItem], 40 | section: &str, 41 | current_page: &BookItem, 42 | out: &mut Write) -> io::Result<()> { 43 | for (i, item) in items.iter().enumerate() { 44 | walk_item(item, &format!("{}{}.", section, i + 1)[..], current_page, out)?; 45 | } 46 | Ok(()) 47 | } 48 | fn walk_item(item: &BookItem, 49 | section: &str, 50 | current_page: &BookItem, 51 | out: &mut Write) -> io::Result<()> { 52 | let class_string = if item.path == current_page.path { 53 | "class='active'" 54 | } else { 55 | "" 56 | }; 57 | 58 | writeln!(out, "
  • {} {}", 59 | class_string, 60 | current_page.path_to_root.join(&item.path).with_extension("html").display(), 61 | section, 62 | item.title)?; 63 | if !item.children.is_empty() { 64 | writeln!(out, "
      ")?; 65 | let _ = walk_items(&item.children[..], section, current_page, out); 66 | writeln!(out, "
    ")?; 67 | } 68 | writeln!(out, "
  • ")?; 69 | 70 | Ok(()) 71 | } 72 | 73 | writeln!(out, "
    ")?; 74 | writeln!(out, "
      ")?; 75 | walk_items(&book.chapters[..], "", ¤t_page, out)?; 76 | writeln!(out, "
    ")?; 77 | writeln!(out, "
    ")?; 78 | 79 | Ok(()) 80 | } 81 | 82 | fn render(book: &Book, tgt: &Path) -> CliResult<()> { 83 | let tmp = TempDir::new("rustbook")?; 84 | 85 | for (_section, item) in book.iter() { 86 | let out_path = match item.path.parent() { 87 | Some(p) => tgt.join(p), 88 | None => tgt.to_path_buf(), 89 | }; 90 | 91 | let src; 92 | if env::args().len() < 3 { 93 | src = env::current_dir().unwrap().clone(); 94 | } else { 95 | src = PathBuf::from(&env::args().nth(2).unwrap()); 96 | } 97 | // preprocess the markdown, rerouting markdown references to html 98 | // references 99 | let mut markdown_data = String::new(); 100 | File::open(&src.join(&item.path)).and_then(|mut f| { 101 | f.read_to_string(&mut markdown_data) 102 | })?; 103 | let preprocessed_path = tmp.path().join(item.path.file_name().unwrap()); 104 | { 105 | let urls = markdown_data.replace(".md)", ".html)"); 106 | File::create(&preprocessed_path).and_then(|mut f| { 107 | f.write_all(urls.as_bytes()) 108 | })?; 109 | } 110 | 111 | // write the prelude to a temporary HTML file for rustdoc inclusion 112 | let prelude = tmp.path().join("prelude.html"); 113 | { 114 | let mut buffer = BufWriter::new(File::create(&prelude)?); 115 | writeln!(&mut buffer, r#" 116 | "#)?; 124 | let _ = write_toc(book, &item, &mut buffer); 125 | writeln!(&mut buffer, "
    ")?; 126 | writeln!(&mut buffer, "
    ")?; 127 | } 128 | 129 | // write the postlude to a temporary HTML file for rustdoc inclusion 130 | let postlude = tmp.path().join("postlude.html"); 131 | { 132 | let mut buffer = BufWriter::new(File::create(&postlude)?); 133 | writeln!(&mut buffer, "")?; 134 | writeln!(&mut buffer, "
    ")?; 135 | } 136 | 137 | fs::create_dir_all(&out_path)?; 138 | 139 | let rustdoc_args: &[String] = &[ 140 | "".to_string(), 141 | preprocessed_path.display().to_string(), 142 | format!("-o{}", out_path.display()), 143 | format!("--html-before-content={}", prelude.display()), 144 | format!("--html-after-content={}", postlude.display()), 145 | format!("--markdown-playground-url=https://play.rust-lang.org/"), 146 | format!("--markdown-css={}", item.path_to_root.join("rustbook.css").display()), 147 | "--markdown-no-toc".to_string(), 148 | ]; 149 | let output_result = rustdoc::main_args(rustdoc_args); 150 | if output_result != 0 { 151 | let message = format!("Could not execute `rustdoc` with {:?}: {}", 152 | rustdoc_args, output_result); 153 | return Err(err(&message)); 154 | } 155 | } 156 | 157 | // create index.html from the root README 158 | fs::copy(&tgt.join("README.html"), &tgt.join("index.html"))?; 159 | 160 | Ok(()) 161 | } 162 | 163 | impl Subcommand for Build { 164 | fn parse_args(&mut self, _: &[String]) -> CliResult<()> { 165 | Ok(()) 166 | } 167 | fn usage(&self) {} 168 | fn execute(&mut self, term: &mut Term) -> CommandResult<()> { 169 | let cwd = env::current_dir().unwrap(); 170 | let src; 171 | let tgt; 172 | 173 | if env::args().len() < 3 { 174 | src = cwd.clone(); 175 | } else { 176 | src = PathBuf::from(&env::args().nth(2).unwrap()); 177 | } 178 | 179 | if env::args().len() < 4 { 180 | tgt = cwd.join("_book"); 181 | } else { 182 | tgt = PathBuf::from(&env::args().nth(3).unwrap()); 183 | } 184 | 185 | // `_book` directory may already exist from previous runs. Check and 186 | // delete it if it exists. 187 | for entry in fs::read_dir(&cwd)? { 188 | let path = entry?.path(); 189 | if path == tgt { fs::remove_dir_all(&tgt)? } 190 | } 191 | fs::create_dir(&tgt)?; 192 | 193 | // Copy static files 194 | let css = include_bytes!("static/rustbook.css"); 195 | let js = include_bytes!("static/rustbook.js"); 196 | 197 | let mut css_file = File::create(tgt.join("rustbook.css"))?; 198 | css_file.write_all(css)?; 199 | 200 | let mut js_file = File::create(tgt.join("rustbook.js"))?; 201 | js_file.write_all(js)?; 202 | 203 | 204 | let mut summary = File::open(&src.join("SUMMARY.md"))?; 205 | match book::parse_summary(&mut summary, &src) { 206 | Ok(book) => { 207 | // execute rustdoc on the whole book 208 | render(&book, &tgt) 209 | } 210 | Err(errors) => { 211 | let n = errors.len(); 212 | for err in errors { 213 | term.err(&format!("error: {}", err)[..]); 214 | } 215 | 216 | Err(err(&format!("{} errors occurred", n))) 217 | } 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/css.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | // The rust-book CSS in string form. 12 | 13 | pub static STYLE: &'static str = r#" 14 | @import url("../rust.css"); 15 | 16 | body { 17 | max-width:none; 18 | } 19 | 20 | @media only screen { 21 | #toc { 22 | position: absolute; 23 | left: 0px; 24 | top: 0px; 25 | bottom: 0px; 26 | width: 250px; 27 | overflow-y: auto; 28 | border-right: 1px solid rgba(0, 0, 0, 0.07); 29 | padding: 10px 10px; 30 | font-size: 16px; 31 | background: none repeat scroll 0% 0% #FFF; 32 | box-sizing: border-box; 33 | -webkit-overflow-scrolling: touch; 34 | } 35 | 36 | #page-wrapper { 37 | position: absolute; 38 | overflow-y: auto; 39 | left: 260px; 40 | right: 0px; 41 | top: 0px; 42 | bottom: 0px; 43 | box-sizing: border-box; 44 | background: none repeat scroll 0% 0% #FFF; 45 | -webkit-overflow-scrolling: touch; 46 | } 47 | } 48 | 49 | @media only print { 50 | #toc, #nav { 51 | display: none; 52 | } 53 | } 54 | 55 | @media only screen and (max-width: 1060px) { 56 | #toc { 57 | width: 100%; 58 | margin-right: 0; 59 | top: 40px; 60 | } 61 | #page-wrapper { 62 | top: 40px; 63 | left: 15px; 64 | padding-right: 15px; 65 | } 66 | .mobile-hidden { 67 | display: none; 68 | } 69 | } 70 | 71 | #page { 72 | margin-left: auto; 73 | margin-right:auto; 74 | max-width: 750px; 75 | padding-bottom: 50px; 76 | } 77 | 78 | .chapter { 79 | list-style: none outside none; 80 | padding-left: 0px; 81 | line-height: 30px; 82 | } 83 | 84 | .section { 85 | list-style: none outside none; 86 | padding-left: 20px; 87 | line-height: 30px; 88 | } 89 | 90 | .section li { 91 | text-overflow: ellipsis; 92 | overflow: hidden; 93 | white-space: nowrap; 94 | } 95 | 96 | .chapter li a { 97 | color: #000000; 98 | } 99 | 100 | .chapter li a.active { 101 | text-decoration: underline; 102 | font-weight: bold; 103 | } 104 | 105 | #toggle-nav { 106 | height: 20px; 107 | width: 30px; 108 | padding: 3px 3px 0 3px; 109 | } 110 | 111 | #toggle-nav { 112 | margin-top: 5px; 113 | width: 30px; 114 | height: 30px; 115 | background-color: #FFF; 116 | border: 1px solid #666; 117 | border-radius: 3px 3px 3px 3px; 118 | } 119 | 120 | .sr-only { 121 | position: absolute; 122 | width: 1px; 123 | height: 1px; 124 | margin: -1px; 125 | padding: 0; 126 | overflow: hidden; 127 | clip: rect(0, 0, 0, 0); 128 | border: 0; 129 | } 130 | 131 | .bar { 132 | display: block; 133 | background-color: #000; 134 | border-radius: 2px; 135 | width: 100%; 136 | height: 2px; 137 | margin: 2px 0 3px; 138 | padding: 0; 139 | } 140 | 141 | .left { 142 | float: left; 143 | } 144 | 145 | .right { 146 | float: right; 147 | } 148 | "#; 149 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Error handling utilities. WIP. 12 | 13 | use std::error::Error; 14 | use std::fmt; 15 | 16 | pub type CliError = Box; 17 | pub type CliResult = Result; 18 | 19 | pub type CommandError = Box; 20 | pub type CommandResult = Result; 21 | 22 | pub fn err(s: &str) -> CliError { 23 | #[derive(Debug)] 24 | struct E(String); 25 | 26 | impl Error for E { 27 | fn description(&self) -> &str { &self.0 } 28 | } 29 | impl fmt::Display for E { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | self.0.fmt(f) 32 | } 33 | } 34 | 35 | Box::new(E(s.to_string())) 36 | } 37 | -------------------------------------------------------------------------------- /src/help.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Implementation of the `help` subcommand. Currently just prints basic usage info. 12 | 13 | use subcommand::Subcommand; 14 | use error::CliResult; 15 | use error::CommandResult; 16 | use term::Term; 17 | 18 | struct Help; 19 | 20 | pub fn parse_cmd(name: &str) -> Option> { 21 | match name { 22 | "help" | "--help" | "-h" | "-?" => Some(Box::new(Help)), 23 | _ => None 24 | } 25 | } 26 | 27 | impl Subcommand for Help { 28 | fn parse_args(&mut self, _: &[String]) -> CliResult<()> { 29 | Ok(()) 30 | } 31 | fn usage(&self) {} 32 | fn execute(&mut self, _: &mut Term) -> CommandResult<()> { 33 | usage(); 34 | Ok(()) 35 | } 36 | } 37 | 38 | pub fn usage() { 39 | println!("Usage: rustbook []"); 40 | println!(""); 41 | println!("The must be one of:"); 42 | println!(" help Print this message."); 43 | println!(" build Build the book in subdirectory _book"); 44 | println!(" serve --NOT YET IMPLEMENTED--"); 45 | println!(" test --NOT YET IMPLEMENTED--"); 46 | } 47 | -------------------------------------------------------------------------------- /src/javascript.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | // The rust-book JavaScript in string form. 12 | 13 | pub static JAVASCRIPT: &'static str = r#" 14 | 76 | 77 | "#; 78 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | #![deny(warnings)] 12 | 13 | #![feature(rustc_private)] 14 | #![feature(rustdoc)] 15 | 16 | extern crate rustdoc; 17 | extern crate rustc_back; 18 | 19 | use std::env; 20 | use std::process; 21 | use std::sync::atomic::{AtomicIsize, ATOMIC_ISIZE_INIT, Ordering}; 22 | use term::Term; 23 | 24 | mod term; 25 | mod error; 26 | mod book; 27 | 28 | mod subcommand; 29 | mod help; 30 | mod build; 31 | mod serve; 32 | mod test; 33 | 34 | static EXIT_STATUS: AtomicIsize = ATOMIC_ISIZE_INIT; 35 | 36 | pub fn main() { 37 | let mut term = Term::new(); 38 | let cmd: Vec<_> = env::args().collect(); 39 | 40 | if cmd.len() <= 1 { 41 | help::usage() 42 | } else { 43 | match subcommand::parse_name(&cmd[1][..]) { 44 | Some(mut subcmd) => { 45 | match subcmd.parse_args(&cmd[..cmd.len()-1]) { 46 | Ok(_) => { 47 | match subcmd.execute(&mut term) { 48 | Ok(_) => (), 49 | Err(err) => { 50 | term.err(&format!("error: {}", err)); 51 | } 52 | } 53 | } 54 | Err(err) => { 55 | println!("{}", err.description()); 56 | println!(""); 57 | subcmd.usage(); 58 | } 59 | } 60 | } 61 | None => { 62 | println!("Unrecognized command '{}'.", cmd[1]); 63 | println!(""); 64 | help::usage(); 65 | } 66 | } 67 | } 68 | process::exit(EXIT_STATUS.load(Ordering::SeqCst) as i32); 69 | } 70 | -------------------------------------------------------------------------------- /src/serve.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Implementation of the `serve` subcommand. Just a stub for now. 12 | 13 | use subcommand::Subcommand; 14 | use error::CliResult; 15 | use error::CommandResult; 16 | use term::Term; 17 | 18 | struct Serve; 19 | 20 | pub fn parse_cmd(name: &str) -> Option> { 21 | if name == "serve" { 22 | Some(Box::new(Serve)) 23 | } else { 24 | None 25 | } 26 | } 27 | 28 | impl Subcommand for Serve { 29 | fn parse_args(&mut self, _: &[String]) -> CliResult<()> { 30 | Ok(()) 31 | } 32 | fn usage(&self) {} 33 | fn execute(&mut self, _: &mut Term) -> CommandResult<()> { 34 | Ok(()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/static/rustbook.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT 3 | * file at the top-level directory of this distribution and at 4 | * http://rust-lang.org/COPYRIGHT. 5 | * 6 | * Licensed under the Apache License, Version 2.0 or the MIT license 8 | * , at your 9 | * option. This file may not be copied, modified, or distributed 10 | * except according to those terms. 11 | */ 12 | 13 | @import url('../rust.css'); 14 | 15 | body { 16 | max-width: none; 17 | font: 16px/1.6 'Source Serif Pro', Georgia, Times, 'Times New Roman', serif; 18 | color: #333; 19 | } 20 | 21 | h1, h2, h3, h4, h5, h6 { 22 | font-family: 'Open Sans', 'Fira Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; 23 | font-weight: bold; 24 | color: #333; 25 | } 26 | 27 | @media only screen { 28 | #toc { 29 | position: fixed; 30 | top: 0; 31 | left: 0; 32 | bottom: 0; 33 | width: 300px; 34 | overflow-y: auto; 35 | border-right: 1px solid #e8e8e8; 36 | padding: 0 15px; 37 | font-size: 14px; 38 | background-color: #fafafa; 39 | -webkit-overflow-scrolling: touch; 40 | } 41 | 42 | #page-wrapper { 43 | position: absolute; 44 | top: 0; 45 | left: 300px; 46 | right: 0; 47 | padding: 0 15px; 48 | -webkit-overflow-scrolling: touch; 49 | } 50 | } 51 | 52 | @media only print { 53 | #toc, #nav { 54 | display: none; 55 | } 56 | } 57 | 58 | @media only screen and (max-width: 1023px) { 59 | #toc { 60 | width: 100%; 61 | top: 40px; 62 | } 63 | 64 | #page-wrapper { 65 | top: 40px; 66 | left: 0; 67 | } 68 | 69 | .mobile-hidden { 70 | display: none; 71 | } 72 | } 73 | 74 | #page { 75 | margin: 0 auto; 76 | max-width: 750px; 77 | padding-bottom: 50px; 78 | } 79 | 80 | .chapter { 81 | list-style: none; 82 | padding-left: 0; 83 | line-height: 30px; 84 | } 85 | 86 | .section { 87 | list-style: none; 88 | padding-left: 20px; 89 | line-height: 40px; 90 | } 91 | 92 | .section li { 93 | text-overflow: ellipsis; 94 | overflow: hidden; 95 | white-space: nowrap; 96 | } 97 | 98 | .chapter li a { 99 | color: #333; 100 | padding: 5px 0; 101 | } 102 | 103 | .chapter li a.active, 104 | .chapter li a:hover { 105 | color: #008cff; 106 | text-decoration: none; 107 | } 108 | 109 | #toggle-nav { 110 | cursor: pointer; 111 | margin-top: 5px; 112 | width: 30px; 113 | height: 30px; 114 | background-color: #fff; 115 | border: 1px solid #666; 116 | border-radius: 3px; 117 | padding: 3px 3px 0 3px; 118 | } 119 | 120 | .sr-only { 121 | position: absolute; 122 | width: 1px; 123 | height: 1px; 124 | margin: -1px; 125 | padding: 0; 126 | overflow: hidden; 127 | clip: rect(0, 0, 0, 0); 128 | border: 0; 129 | } 130 | 131 | .bar { 132 | display: block; 133 | background-color: #000; 134 | border-radius: 2px; 135 | width: 100%; 136 | height: 2px; 137 | margin: 2px 0 3px; 138 | padding: 0; 139 | } 140 | 141 | pre { 142 | padding: 11px; 143 | overflow: auto; 144 | font-size: 85%; 145 | line-height: 1.45; 146 | background-color: #f7f7f7; 147 | border: 0; 148 | border-radius: 3px; 149 | } 150 | 151 | .left { 152 | float: left; 153 | } 154 | 155 | .right { 156 | float: right; 157 | } 158 | -------------------------------------------------------------------------------- /src/static/rustbook.js: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | /*jslint browser: true, es5: true */ 12 | /*globals $: true, rootPath: true */ 13 | 14 | document.addEventListener('DOMContentLoaded', function() { 15 | 'use strict'; 16 | 17 | document.getElementById('toggle-nav').onclick = function(e) { 18 | var toc = document.getElementById('toc'); 19 | var pagewrapper = document.getElementById('page-wrapper'); 20 | toggleClass(toc, 'mobile-hidden'); 21 | toggleClass(pagewrapper, 'mobile-hidden'); 22 | }; 23 | 24 | function toggleClass(el, className) { 25 | // from http://youmightnotneedjquery.com/ 26 | if (el.classList) { 27 | el.classList.toggle(className); 28 | } else { 29 | var classes = el.className.split(' '); 30 | var existingIndex = classes.indexOf(className); 31 | 32 | if (existingIndex >= 0) { 33 | classes.splice(existingIndex, 1); 34 | } else { 35 | classes.push(className); 36 | } 37 | 38 | el.className = classes.join(' '); 39 | } 40 | } 41 | 42 | // The below code is used to add prev and next navigation links to the 43 | // bottom of each of the sections. 44 | // It works by extracting the current page based on the url and iterates 45 | // over the menu links until it finds the menu item for the current page. We 46 | // then create a copy of the preceding and following menu links and add the 47 | // correct css class and insert them into the bottom of the page. 48 | var toc = document.getElementById('toc').getElementsByTagName('a'); 49 | var href = document.location.pathname.split('/').pop(); 50 | 51 | if (href === 'index.html' || href === '') { 52 | href = 'README.html'; 53 | } 54 | 55 | for (var i = 0; i < toc.length; i++) { 56 | if (toc[i].attributes.href.value.split('/').pop() === href) { 57 | var nav = document.createElement('p'); 58 | 59 | if (i > 0) { 60 | var prevNode = toc[i-1].cloneNode(true); 61 | prevNode.className = 'left'; 62 | prevNode.setAttribute('rel', 'prev'); 63 | nav.appendChild(prevNode); 64 | } 65 | 66 | if (i < toc.length - 1) { 67 | var nextNode = toc[i+1].cloneNode(true); 68 | nextNode.className = 'right'; 69 | nextNode.setAttribute('rel', 'next'); 70 | nav.appendChild(nextNode); 71 | } 72 | 73 | document.getElementById('page').appendChild(nav); 74 | 75 | break; 76 | } 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /src/subcommand.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Common API for all rustbook subcommands. 12 | 13 | use error::CliResult; 14 | use error::CommandResult; 15 | use term::Term; 16 | 17 | use help; 18 | use build; 19 | use serve; 20 | use test; 21 | 22 | pub trait Subcommand { 23 | /// Mutate the subcommand by parsing its arguments. 24 | /// 25 | /// Returns `Err` on a parsing error. 26 | fn parse_args(&mut self, args: &[String]) -> CliResult<()>; 27 | /// Print the CLI usage information. 28 | fn usage(&self); 29 | /// Actually execute the subcommand. 30 | fn execute(&mut self, term: &mut Term) -> CommandResult<()>; 31 | } 32 | 33 | /// Create a Subcommand object based on its name. 34 | pub fn parse_name(name: &str) -> Option> { 35 | let cmds: [fn(&str) -> Option>; 4] = [help::parse_cmd, 36 | build::parse_cmd, 37 | serve::parse_cmd, 38 | test::parse_cmd]; 39 | for parser in &cmds { 40 | let parsed = (*parser)(name); 41 | if parsed.is_some() { return parsed } 42 | } 43 | None 44 | } 45 | -------------------------------------------------------------------------------- /src/term.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! An abstraction of the terminal. Eventually, provide color and 12 | //! verbosity support. For now, just a wrapper around stdout/stderr. 13 | 14 | use std::io; 15 | use std::io::prelude::*; 16 | use std::sync::atomic::Ordering; 17 | 18 | pub struct Term { 19 | err: Box 20 | } 21 | 22 | impl Term { 23 | pub fn new() -> Term { 24 | Term { 25 | err: Box::new(io::stderr()) 26 | } 27 | } 28 | 29 | pub fn err(&mut self, msg: &str) { 30 | // swallow any errors 31 | let _ = writeln!(&mut self.err, "{}", msg); 32 | ::EXIT_STATUS.store(101, Ordering::SeqCst); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Implementation of the `test` subcommand. Just a stub for now. 12 | 13 | use subcommand::Subcommand; 14 | use error::{err, CliResult, CommandResult}; 15 | use term::Term; 16 | use book; 17 | 18 | use std::fs::File; 19 | use std::env; 20 | use std::process::Command; 21 | 22 | struct Test; 23 | 24 | pub fn parse_cmd(name: &str) -> Option> { 25 | if name == "test" { 26 | Some(Box::new(Test)) 27 | } else { 28 | None 29 | } 30 | } 31 | 32 | impl Subcommand for Test { 33 | fn parse_args(&mut self, _: &[String]) -> CliResult<()> { 34 | Ok(()) 35 | } 36 | fn usage(&self) {} 37 | fn execute(&mut self, term: &mut Term) -> CommandResult<()> { 38 | let cwd = env::current_dir().unwrap(); 39 | let src = cwd.clone(); 40 | 41 | let mut summary = File::open(&src.join("SUMMARY.md"))?; 42 | match book::parse_summary(&mut summary, &src) { 43 | Ok(book) => { 44 | for (_, item) in book.iter() { 45 | let output_result = Command::new("rustdoc") 46 | .arg(&item.path) 47 | .arg("--test") 48 | .output(); 49 | match output_result { 50 | Ok(output) => { 51 | if !output.status.success() { 52 | term.err(&format!("{}\n{}", 53 | String::from_utf8_lossy(&output.stdout), 54 | String::from_utf8_lossy(&output.stderr))); 55 | return Err(err("some tests failed")); 56 | } 57 | 58 | } 59 | Err(e) => { 60 | let message = format!("could not execute `rustdoc`: {}", e); 61 | return Err(err(&message)) 62 | } 63 | } 64 | } 65 | } 66 | Err(errors) => { 67 | for err in errors { 68 | term.err(&err[..]); 69 | } 70 | return Err(err("there was an error")) 71 | } 72 | } 73 | Ok(()) // lol 74 | } 75 | } 76 | --------------------------------------------------------------------------------