]> git.lizzy.rs Git - rust.git/blob - src/rustbook/book.rs
doc: make String::as_bytes example more simple
[rust.git] / src / rustbook / 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 <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 //! 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<BookItem>,
23 }
24
25 pub struct Book {
26     pub chapters: Vec<BookItem>,
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<Book, Vec<String>> {
81     fn collapse(stack: &mut Vec<BookItem>,
82                 top_items: &mut Vec<BookItem>,
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).relative_from(src) {
130             Some(p) => p.to_path_buf(),
131             None => {
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::<String>());
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::<usize>() / 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 }