]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
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 = TempDir::new("rust-book") | |
77 | .ok() | |
78 | // FIXME: lift to Result instead | |
79 | .expect("could not create temporary directory"); | |
80 | ||
81 | for (section, item) in book.iter() { | |
82 | println!("{} {}", section, item.title); | |
83 | ||
84 | let out_path = tgt.join(item.path.dirname()); | |
85 | ||
86 | let regex = r"\[(?P<title>[^]]*)\]\((?P<url_stem>[^)]*)\.(?P<ext>md|markdown)\)"; | |
87 | let md_urls = Regex::new(regex).unwrap(); | |
88 | ||
89 | let src; | |
90 | if os::args().len() < 3 { | |
91 | src = os::getcwd().unwrap().clone(); | |
92 | } else { | |
93 | src = Path::new(os::args()[2].clone()); | |
94 | } | |
95 | // preprocess the markdown, rerouting markdown references to html references | |
96 | let markdown_data = try!(File::open(&src.join(&item.path)).read_to_string()); | |
97 | let preprocessed_path = tmp.path().join(item.path.filename().unwrap()); | |
98 | { | |
99 | let urls = md_urls.replace_all(&markdown_data[], "[$title]($url_stem.html)"); | |
100 | try!(File::create(&preprocessed_path) | |
101 | .write_str(&urls[])); | |
102 | } | |
103 | ||
104 | // write the prelude to a temporary HTML file for rustdoc inclusion | |
105 | let prelude = tmp.path().join("prelude.html"); | |
106 | { | |
107 | let mut toc = BufferedWriter::new(try!(File::create(&prelude))); | |
108 | let _ = write_toc(book, &item.path_to_root, &mut toc); | |
109 | try!(writeln!(&mut toc, "<div id='page-wrapper'>")); | |
110 | try!(writeln!(&mut toc, "<div id='page'>")); | |
111 | } | |
112 | ||
113 | // write the postlude to a temporary HTML file for rustdoc inclusion | |
114 | let postlude = tmp.path().join("postlude.html"); | |
115 | { | |
116 | let mut toc = BufferedWriter::new(try!(File::create(&postlude))); | |
117 | try!(writeln!(&mut toc, "</div></div>")); | |
118 | } | |
119 | ||
120 | try!(fs::mkdir_recursive(&out_path, io::USER_DIR)); | |
121 | ||
122 | let rustdoc_args: &[String] = &[ | |
123 | "".to_string(), | |
124 | preprocessed_path.display().to_string(), | |
125 | format!("-o{}", out_path.display()), | |
126 | format!("--html-before-content={}", prelude.display()), | |
127 | format!("--html-after-content={}", postlude.display()), | |
128 | format!("--markdown-css={}", item.path_to_root.join("rust-book.css").display()), | |
129 | "--markdown-no-toc".to_string(), | |
130 | ]; | |
131 | let output_result = rustdoc::main_args(rustdoc_args); | |
132 | if output_result != 0 { | |
133 | let message = format!("Could not execute `rustdoc` with {:?}: {}", | |
134 | rustdoc_args, output_result); | |
135 | return Err(box message as Box<Error>); | |
136 | } | |
137 | } | |
138 | ||
139 | // create index.html from the root README | |
140 | try!(fs::copy(&tgt.join("README.html"), &tgt.join("index.html"))); | |
141 | Ok(()) | |
142 | } | |
143 | ||
144 | impl Subcommand for Build { | |
145 | fn parse_args(&mut self, _: &[String]) -> CliResult<()> { | |
146 | Ok(()) | |
147 | } | |
148 | fn usage(&self) {} | |
149 | fn execute(&mut self, term: &mut Term) -> CommandResult<()> { | |
150 | let cwd = os::getcwd().unwrap(); | |
151 | let src; | |
152 | let tgt; | |
153 | ||
154 | if os::args().len() < 3 { | |
155 | src = cwd.clone(); | |
156 | } else { | |
157 | src = Path::new(os::args()[2].clone()); | |
158 | } | |
159 | ||
160 | if os::args().len() < 4 { | |
161 | tgt = cwd.join("_book"); | |
162 | } else { | |
163 | tgt = Path::new(os::args()[3].clone()); | |
164 | } | |
165 | ||
166 | let _ = fs::mkdir(&tgt, io::USER_DIR); // FIXME: handle errors | |
167 | ||
168 | // FIXME: handle errors | |
169 | let _ = File::create(&tgt.join("rust-book.css")).write_str(css::STYLE); | |
170 | ||
171 | let summary = File::open(&src.join("SUMMARY.md")); | |
172 | match book::parse_summary(summary, &src) { | |
173 | Ok(book) => { | |
174 | // execute rustdoc on the whole book | |
175 | try!(render(&book, &tgt).map_err(|err| { | |
176 | term.err(&format!("error: {}", err.description())[]); | |
177 | err.detail().map(|detail| { | |
178 | term.err(&format!("detail: {}", detail)[]); | |
179 | }); | |
180 | err | |
181 | })) | |
182 | } | |
183 | Err(errors) => { | |
184 | for err in errors.into_iter() { | |
185 | term.err(&err[]); | |
186 | } | |
187 | } | |
188 | } | |
189 | ||
190 | Ok(()) // lol | |
191 | } | |
192 | } |