]> git.lizzy.rs Git - rust.git/blob - src/rustbook/build.rs
mk: The beta channel produces things called 'beta'
[rust.git] / src / rustbook / build.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 <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
13 use std::os;
14 use std::io;
15 use std::io::{fs, File, BufferedWriter, TempDir, IoResult};
16
17 use subcommand::Subcommand;
18 use term::Term;
19 use error::{Error, CliResult, CommandResult};
20 use book;
21 use book::{Book, BookItem};
22 use css;
23
24 use regex::Regex;
25
26 use rustdoc;
27
28 struct Build;
29
30 pub fn parse_cmd(name: &str) -> Option<Box<Subcommand>> {
31     if name == "build" {
32         Some(box Build as Box<Subcommand>)
33     } else {
34         None
35     }
36 }
37
38 fn write_toc(book: &Book, path_to_root: &Path, out: &mut Writer) -> IoResult<()> {
39     fn walk_items(items: &[BookItem],
40                   section: &str,
41                   path_to_root: &Path,
42                   out: &mut Writer) -> IoResult<()> {
43         for (i, item) in items.iter().enumerate() {
44             try!(walk_item(item, &format!("{}{}.", section, i + 1)[], path_to_root, out));
45         }
46         Ok(())
47     }
48     fn walk_item(item: &BookItem,
49                  section: &str,
50                  path_to_root: &Path,
51                  out: &mut Writer) -> IoResult<()> {
52         try!(writeln!(out, "<li><a href='{}'><b>{}</b> {}</a>",
53                  path_to_root.join(item.path.with_extension("html")).display(),
54                  section,
55                  item.title));
56         if !item.children.is_empty() {
57             try!(writeln!(out, "<ul class='section'>"));
58             let _ = walk_items(&item.children[], section, path_to_root, out);
59             try!(writeln!(out, "</ul>"));
60         }
61         try!(writeln!(out, "</li>"));
62
63         Ok(())
64     }
65
66     try!(writeln!(out, "<div id='toc'>"));
67     try!(writeln!(out, "<ul class='chapter'>"));
68     try!(walk_items(&book.chapters[], "", path_to_root, out));
69     try!(writeln!(out, "</ul>"));
70     try!(writeln!(out, "</div>"));
71
72     Ok(())
73 }
74
75 fn render(book: &Book, tgt: &Path) -> CliResult<()> {
76     let tmp = try!(TempDir::new("rust-book"));
77
78     for (section, item) in book.iter() {
79         println!("{} {}", section, item.title);
80
81         let out_path = tgt.join(item.path.dirname());
82
83         let regex = r"\[(?P<title>[^]]*)\]\((?P<url_stem>[^)]*)\.(?P<ext>md|markdown)\)";
84         let md_urls = Regex::new(regex).unwrap();
85
86         let src;
87         if os::args().len() < 3 {
88             src = os::getcwd().unwrap().clone();
89         } else {
90             src = Path::new(os::args()[2].clone());
91         }
92         // preprocess the markdown, rerouting markdown references to html references
93         let markdown_data = try!(File::open(&src.join(&item.path)).read_to_string());
94         let preprocessed_path = tmp.path().join(item.path.filename().unwrap());
95         {
96             let urls = md_urls.replace_all(&markdown_data[], "[$title]($url_stem.html)");
97             try!(File::create(&preprocessed_path)
98                       .write_str(&urls[]));
99         }
100
101         // write the prelude to a temporary HTML file for rustdoc inclusion
102         let prelude = tmp.path().join("prelude.html");
103         {
104             let mut toc = BufferedWriter::new(try!(File::create(&prelude)));
105             let _ = write_toc(book, &item.path_to_root, &mut toc);
106             try!(writeln!(&mut toc, "<div id='page-wrapper'>"));
107             try!(writeln!(&mut toc, "<div id='page'>"));
108         }
109
110         // write the postlude to a temporary HTML file for rustdoc inclusion
111         let postlude = tmp.path().join("postlude.html");
112         {
113             let mut toc = BufferedWriter::new(try!(File::create(&postlude)));
114             try!(writeln!(&mut toc, "</div></div>"));
115         }
116
117         try!(fs::mkdir_recursive(&out_path, io::USER_DIR));
118
119         let rustdoc_args: &[String] = &[
120             "".to_string(),
121             preprocessed_path.display().to_string(),
122             format!("-o{}", out_path.display()),
123             format!("--html-before-content={}", prelude.display()),
124             format!("--html-after-content={}", postlude.display()),
125             format!("--markdown-css={}", item.path_to_root.join("rust-book.css").display()),
126             "--markdown-no-toc".to_string(),
127         ];
128         let output_result = rustdoc::main_args(rustdoc_args);
129         if output_result != 0 {
130             let message = format!("Could not execute `rustdoc` with {:?}: {}",
131                                   rustdoc_args, output_result);
132             return Err(box message as Box<Error>);
133         }
134     }
135
136     // create index.html from the root README
137     try!(fs::copy(&tgt.join("README.html"), &tgt.join("index.html")));
138     Ok(())
139 }
140
141 impl Subcommand for Build {
142     fn parse_args(&mut self, _: &[String]) -> CliResult<()> {
143         Ok(())
144     }
145     fn usage(&self) {}
146     fn execute(&mut self, term: &mut Term) -> CommandResult<()> {
147         let cwd = os::getcwd().unwrap();
148         let src;
149         let tgt;
150
151         if os::args().len() < 3 {
152             src = cwd.clone();
153         } else {
154             src = Path::new(os::args()[2].clone());
155         }
156
157         if os::args().len() < 4 {
158             tgt = cwd.join("_book");
159         } else {
160             tgt = Path::new(os::args()[3].clone());
161         }
162
163         try!(fs::mkdir(&tgt, io::USER_DIR));
164
165         try!(File::create(&tgt.join("rust-book.css")).write_str(css::STYLE));
166
167         let summary = try!(File::open(&src.join("SUMMARY.md")));
168         match book::parse_summary(summary, &src) {
169             Ok(book) => {
170                 // execute rustdoc on the whole book
171                 render(&book, &tgt)
172             }
173             Err(errors) => {
174                 let n = errors.len();
175                 for err in errors.into_iter() {
176                     term.err(&format!("error: {}", err)[]);
177                 }
178
179                 Err(box format!("{} errors occurred", n) as Box<Error>)
180             }
181         }
182     }
183 }