]>
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 | //! Basic data structures for representing a book. | |
12 | ||
c34b1796 AL |
13 | use std::io::prelude::*; |
14 | use std::io::BufReader; | |
1a4d82fc | 15 | use std::iter; |
c34b1796 | 16 | use std::path::{Path, PathBuf}; |
1a4d82fc JJ |
17 | |
18 | pub struct BookItem { | |
19 | pub title: String, | |
c34b1796 AL |
20 | pub path: PathBuf, |
21 | pub path_to_root: PathBuf, | |
1a4d82fc JJ |
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(); | |
85aaf69f | 53 | for &(_, idx) in &self.stack { |
c34b1796 | 54 | section.push_str(&(idx + 1).to_string()[..]); |
1a4d82fc JJ |
55 | section.push('.'); |
56 | } | |
c34b1796 | 57 | section.push_str(&(self.cur_idx + 1).to_string()[..]); |
1a4d82fc JJ |
58 | section.push('.'); |
59 | ||
60 | self.stack.push((self.cur_items, self.cur_idx)); | |
c34b1796 | 61 | self.cur_items = &cur.children[..]; |
1a4d82fc JJ |
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 { | |
c34b1796 | 72 | cur_items: &self.chapters[..], |
1a4d82fc JJ |
73 | cur_idx: 0, |
74 | stack: Vec::new(), | |
75 | } | |
76 | } | |
77 | } | |
78 | ||
79 | /// Construct a book by parsing a summary (markdown table of contents). | |
c34b1796 | 80 | pub fn parse_summary(input: &mut Read, src: &Path) -> Result<Book, Vec<String>> { |
1a4d82fc JJ |
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 | ||
1a4d82fc JJ |
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(), | |
c34b1796 | 104 | path: PathBuf::from("README.md"), |
e9174d1e | 105 | path_to_root: PathBuf::from(""), |
1a4d82fc JJ |
106 | children: vec!(), |
107 | }); | |
108 | ||
c34b1796 | 109 | for line_result in BufReader::new(input).lines() { |
1a4d82fc JJ |
110 | let line = match line_result { |
111 | Ok(line) => line, | |
112 | Err(err) => { | |
c34b1796 | 113 | errors.push(err.to_string()); |
1a4d82fc JJ |
114 | return Err(errors); |
115 | } | |
116 | }; | |
117 | ||
c34b1796 | 118 | let star_idx = match line.find("*") { Some(i) => i, None => continue }; |
85aaf69f | 119 | |
c34b1796 AL |
120 | let start_bracket = star_idx + line[star_idx..].find("[").unwrap(); |
121 | let end_bracket = start_bracket + line[start_bracket..].find("](").unwrap(); | |
85aaf69f | 122 | let start_paren = end_bracket + 1; |
c34b1796 | 123 | let end_paren = start_paren + line[start_paren..].find(")").unwrap(); |
85aaf69f SL |
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]; | |
1a4d82fc | 128 | |
9cc50fc6 SL |
129 | let path_from_root = match src.join(given_path).strip_prefix(src) { |
130 | Ok(p) => p.to_path_buf(), | |
131 | Err(..) => { | |
85aaf69f SL |
132 | errors.push(format!("paths in SUMMARY.md must be relative, \ |
133 | but path '{}' for section '{}' is not.", | |
134 | given_path, title)); | |
c34b1796 | 135 | PathBuf::new() |
1a4d82fc | 136 | } |
85aaf69f | 137 | }; |
c34b1796 | 138 | let path_to_root = PathBuf::from(&iter::repeat("../") |
85aaf69f SL |
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 | } | |
9346a6ac | 153 | }).sum::<usize>() / 4 + 1; |
85aaf69f SL |
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) | |
1a4d82fc JJ |
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 | } |