]>
Commit | Line | Data |
---|---|---|
62682a34 | 1 | // Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT |
1a4d82fc JJ |
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 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 | ||
85aaf69f | 13 | use std::env; |
c34b1796 AL |
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; | |
1a4d82fc JJ |
19 | |
20 | use subcommand::Subcommand; | |
21 | use term::Term; | |
c34b1796 | 22 | use error::{err, CliResult, CommandResult}; |
1a4d82fc JJ |
23 | use book; |
24 | use book::{Book, BookItem}; | |
c1a9b12d | 25 | |
1a4d82fc JJ |
26 | use rustdoc; |
27 | ||
28 | struct Build; | |
29 | ||
30 | pub fn parse_cmd(name: &str) -> Option<Box<Subcommand>> { | |
31 | if name == "build" { | |
c34b1796 | 32 | Some(Box::new(Build)) |
1a4d82fc JJ |
33 | } else { |
34 | None | |
35 | } | |
36 | } | |
37 | ||
62682a34 | 38 | fn write_toc(book: &Book, current_page: &BookItem, out: &mut Write) -> io::Result<()> { |
1a4d82fc JJ |
39 | fn walk_items(items: &[BookItem], |
40 | section: &str, | |
62682a34 | 41 | current_page: &BookItem, |
c34b1796 | 42 | out: &mut Write) -> io::Result<()> { |
1a4d82fc | 43 | for (i, item) in items.iter().enumerate() { |
62682a34 | 44 | try!(walk_item(item, &format!("{}{}.", section, i + 1)[..], current_page, out)); |
1a4d82fc JJ |
45 | } |
46 | Ok(()) | |
47 | } | |
48 | fn walk_item(item: &BookItem, | |
49 | section: &str, | |
62682a34 | 50 | current_page: &BookItem, |
c34b1796 | 51 | out: &mut Write) -> io::Result<()> { |
62682a34 | 52 | let class_string = if item.path == current_page.path { |
e9174d1e | 53 | "class='active'" |
62682a34 | 54 | } else { |
e9174d1e | 55 | "" |
62682a34 SL |
56 | }; |
57 | ||
58 | try!(writeln!(out, "<li><a {} href='{}'><b>{}</b> {}</a>", | |
e9174d1e SL |
59 | class_string, |
60 | current_page.path_to_root.join(&item.path).with_extension("html").display(), | |
61 | section, | |
62 | item.title)); | |
1a4d82fc JJ |
63 | if !item.children.is_empty() { |
64 | try!(writeln!(out, "<ul class='section'>")); | |
62682a34 | 65 | let _ = walk_items(&item.children[..], section, current_page, out); |
1a4d82fc JJ |
66 | try!(writeln!(out, "</ul>")); |
67 | } | |
68 | try!(writeln!(out, "</li>")); | |
69 | ||
70 | Ok(()) | |
71 | } | |
72 | ||
85aaf69f | 73 | try!(writeln!(out, "<div id='toc' class='mobile-hidden'>")); |
1a4d82fc | 74 | try!(writeln!(out, "<ul class='chapter'>")); |
62682a34 | 75 | try!(walk_items(&book.chapters[..], "", ¤t_page, out)); |
1a4d82fc JJ |
76 | try!(writeln!(out, "</ul>")); |
77 | try!(writeln!(out, "</div>")); | |
78 | ||
79 | Ok(()) | |
80 | } | |
81 | ||
82 | fn render(book: &Book, tgt: &Path) -> CliResult<()> { | |
b039eaaf | 83 | let tmp = try!(TempDir::new("rustbook")); |
1a4d82fc | 84 | |
c34b1796 AL |
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 | }; | |
1a4d82fc | 90 | |
1a4d82fc | 91 | let src; |
85aaf69f | 92 | if env::args().len() < 3 { |
c34b1796 | 93 | src = env::current_dir().unwrap().clone(); |
1a4d82fc | 94 | } else { |
c34b1796 | 95 | src = PathBuf::from(&env::args().nth(2).unwrap()); |
1a4d82fc | 96 | } |
9346a6ac AL |
97 | // preprocess the markdown, rerouting markdown references to html |
98 | // references | |
c34b1796 AL |
99 | let mut markdown_data = String::new(); |
100 | try!(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()); | |
1a4d82fc | 104 | { |
85aaf69f | 105 | let urls = markdown_data.replace(".md)", ".html)"); |
c34b1796 AL |
106 | try!(File::create(&preprocessed_path).and_then(|mut f| { |
107 | f.write_all(urls.as_bytes()) | |
108 | })); | |
1a4d82fc JJ |
109 | } |
110 | ||
111 | // write the prelude to a temporary HTML file for rustdoc inclusion | |
112 | let prelude = tmp.path().join("prelude.html"); | |
113 | { | |
b039eaaf SL |
114 | let mut buffer = BufWriter::new(try!(File::create(&prelude))); |
115 | try!(writeln!(&mut buffer, r#" | |
116 | <div id="nav"> | |
117 | <button id="toggle-nav"> | |
118 | <span class="sr-only">Toggle navigation</span> | |
119 | <span class="bar"></span> | |
120 | <span class="bar"></span> | |
121 | <span class="bar"></span> | |
122 | </button> | |
123 | </div>"#)); | |
124 | let _ = write_toc(book, &item, &mut buffer); | |
125 | try!(writeln!(&mut buffer, "<div id='page-wrapper'>")); | |
126 | try!(writeln!(&mut buffer, "<div id='page'>")); | |
1a4d82fc JJ |
127 | } |
128 | ||
129 | // write the postlude to a temporary HTML file for rustdoc inclusion | |
130 | let postlude = tmp.path().join("postlude.html"); | |
131 | { | |
b039eaaf SL |
132 | let mut buffer = BufWriter::new(try!(File::create(&postlude))); |
133 | try!(writeln!(&mut buffer, "<script src='rustbook.js'></script>")); | |
134 | try!(writeln!(&mut buffer, "<script src='playpen.js'></script>")); | |
135 | try!(writeln!(&mut buffer, "</div></div>")); | |
1a4d82fc JJ |
136 | } |
137 | ||
c34b1796 | 138 | try!(fs::create_dir_all(&out_path)); |
1a4d82fc JJ |
139 | |
140 | let rustdoc_args: &[String] = &[ | |
141 | "".to_string(), | |
142 | preprocessed_path.display().to_string(), | |
143 | format!("-o{}", out_path.display()), | |
144 | format!("--html-before-content={}", prelude.display()), | |
145 | format!("--html-after-content={}", postlude.display()), | |
e9174d1e | 146 | format!("--markdown-playground-url=https://play.rust-lang.org"), |
b039eaaf | 147 | format!("--markdown-css={}", item.path_to_root.join("rustbook.css").display()), |
1a4d82fc JJ |
148 | "--markdown-no-toc".to_string(), |
149 | ]; | |
150 | let output_result = rustdoc::main_args(rustdoc_args); | |
151 | if output_result != 0 { | |
152 | let message = format!("Could not execute `rustdoc` with {:?}: {}", | |
153 | rustdoc_args, output_result); | |
c34b1796 | 154 | return Err(err(&message)); |
1a4d82fc JJ |
155 | } |
156 | } | |
157 | ||
158 | // create index.html from the root README | |
159 | try!(fs::copy(&tgt.join("README.html"), &tgt.join("index.html"))); | |
9346a6ac | 160 | |
62682a34 | 161 | // Copy js for playpen |
9346a6ac AL |
162 | let mut playpen = try!(File::create(tgt.join("playpen.js"))); |
163 | let js = include_bytes!("../librustdoc/html/static/playpen.js"); | |
164 | try!(playpen.write_all(js)); | |
1a4d82fc JJ |
165 | Ok(()) |
166 | } | |
167 | ||
168 | impl Subcommand for Build { | |
169 | fn parse_args(&mut self, _: &[String]) -> CliResult<()> { | |
170 | Ok(()) | |
171 | } | |
172 | fn usage(&self) {} | |
173 | fn execute(&mut self, term: &mut Term) -> CommandResult<()> { | |
c34b1796 | 174 | let cwd = env::current_dir().unwrap(); |
1a4d82fc JJ |
175 | let src; |
176 | let tgt; | |
177 | ||
85aaf69f | 178 | if env::args().len() < 3 { |
1a4d82fc JJ |
179 | src = cwd.clone(); |
180 | } else { | |
c34b1796 | 181 | src = PathBuf::from(&env::args().nth(2).unwrap()); |
1a4d82fc JJ |
182 | } |
183 | ||
85aaf69f | 184 | if env::args().len() < 4 { |
1a4d82fc JJ |
185 | tgt = cwd.join("_book"); |
186 | } else { | |
c34b1796 | 187 | tgt = PathBuf::from(&env::args().nth(3).unwrap()); |
1a4d82fc JJ |
188 | } |
189 | ||
d9579d0f AL |
190 | // `_book` directory may already exist from previous runs. Check and |
191 | // delete it if it exists. | |
192 | for entry in try!(fs::read_dir(&cwd)) { | |
193 | let path = try!(entry).path(); | |
194 | if path == tgt { try!(fs::remove_dir_all(&tgt)) } | |
195 | } | |
c34b1796 | 196 | try!(fs::create_dir(&tgt)); |
1a4d82fc | 197 | |
c1a9b12d SL |
198 | // Copy static files |
199 | let css = include_bytes!("static/rustbook.css"); | |
200 | let js = include_bytes!("static/rustbook.js"); | |
201 | ||
b039eaaf | 202 | let mut css_file = try!(File::create(tgt.join("rustbook.css"))); |
c1a9b12d SL |
203 | try!(css_file.write_all(css)); |
204 | ||
b039eaaf | 205 | let mut js_file = try!(File::create(tgt.join("rustbook.js"))); |
c1a9b12d SL |
206 | try!(js_file.write_all(js)); |
207 | ||
1a4d82fc | 208 | |
c34b1796 AL |
209 | let mut summary = try!(File::open(&src.join("SUMMARY.md"))); |
210 | match book::parse_summary(&mut summary, &src) { | |
1a4d82fc JJ |
211 | Ok(book) => { |
212 | // execute rustdoc on the whole book | |
85aaf69f | 213 | render(&book, &tgt) |
1a4d82fc JJ |
214 | } |
215 | Err(errors) => { | |
85aaf69f SL |
216 | let n = errors.len(); |
217 | for err in errors { | |
c34b1796 | 218 | term.err(&format!("error: {}", err)[..]); |
1a4d82fc | 219 | } |
85aaf69f | 220 | |
c34b1796 | 221 | Err(err(&format!("{} errors occurred", n))) |
1a4d82fc JJ |
222 | } |
223 | } | |
1a4d82fc JJ |
224 | } |
225 | } |