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.
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.
11 //! Basic data structures for representing a book.
13 use std::io::prelude::*;
14 use std::io::BufReader;
16 use std::path::{Path, PathBuf};
21 pub path_to_root: PathBuf,
22 pub children: Vec<BookItem>,
26 pub chapters: Vec<BookItem>,
29 /// A depth-first iterator over a book.
30 pub struct BookItems<'a> {
31 cur_items: &'a [BookItem],
33 stack: Vec<(&'a [BookItem], usize)>,
36 impl<'a> Iterator for BookItems<'a> {
37 type Item = (String, &'a BookItem);
39 fn next(&mut self) -> Option<(String, &'a BookItem)> {
41 if self.cur_idx >= self.cur_items.len() {
42 match self.stack.pop() {
44 Some((parent_items, parent_idx)) => {
45 self.cur_items = parent_items;
46 self.cur_idx = parent_idx + 1;
50 let cur = self.cur_items.get(self.cur_idx).unwrap();
52 let mut section = "".to_string();
53 for &(_, idx) in &self.stack {
54 section.push_str(&(idx + 1).to_string()[..]);
57 section.push_str(&(self.cur_idx + 1).to_string()[..]);
60 self.stack.push((self.cur_items, self.cur_idx));
61 self.cur_items = &cur.children[..];
63 return Some((section, cur))
70 pub fn iter(&self) -> BookItems {
72 cur_items: &self.chapters[..],
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>,
85 if stack.len() < to_level { return }
87 top_items.push(stack.pop().unwrap());
91 let tip = stack.pop().unwrap();
92 let last = stack.len() - 1;
93 stack[last].children.push(tip);
97 let mut top_items = vec!();
98 let mut stack = vec!();
99 let mut errors = vec!();
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("."),
109 for line_result in BufReader::new(input).lines() {
110 let line = match line_result {
113 errors.push(err.to_string());
118 let star_idx = match line.find("*") { Some(i) => i, None => continue };
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();
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];
129 let path_from_root = match src.join(given_path).relative_from(src) {
130 Some(p) => p.to_path_buf(),
132 errors.push(format!("paths in SUMMARY.md must be relative, \
133 but path '{}' for section '{}' is not.",
138 let path_to_root = PathBuf::from(&iter::repeat("../")
139 .take(path_from_root.components().count() - 1)
140 .collect::<String>());
141 let item = BookItem {
143 path: path_from_root,
144 path_to_root: path_to_root,
147 let level = indent.chars().map(|c| -> usize {
153 }).sum::<usize>() / 4 + 1;
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);
165 if errors.is_empty() {
166 collapse(&mut stack, &mut top_items, 1);
167 Ok(Book { chapters: top_items })