]> git.proxmox.com Git - rustc.git/blob - src/vendor/mdbook/src/parse/summary.rs
New upstream version 1.24.1+dfsg1
[rustc.git] / src / vendor / mdbook / src / parse / summary.rs
1 use std::path::PathBuf;
2 use std::fs::File;
3 use std::io::{Read, Result, Error, ErrorKind};
4 use book::bookitem::{BookItem, Chapter};
5
6 pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> {
7 debug!("[fn]: construct_bookitems");
8 let mut summary = String::new();
9 File::open(path)?.read_to_string(&mut summary)?;
10
11 debug!("[*]: Parse SUMMARY.md");
12 let top_items = parse_level(&mut summary.split('\n').collect(), 0, vec![0])?;
13 debug!("[*]: Done parsing SUMMARY.md");
14 Ok(top_items)
15 }
16
17 fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32>) -> Result<Vec<BookItem>> {
18 debug!("[fn]: parse_level");
19 let mut items: Vec<BookItem> = vec![];
20
21 // Construct the book recursively
22 while !summary.is_empty() {
23 let item: BookItem;
24 // Indentation level of the line to parse
25 let level = level(summary[0], 4)?;
26
27 // if level < current_level we remove the last digit of section,
28 // exit the current function,
29 // and return the parsed level to the calling function.
30 if level < current_level {
31 break;
32 }
33
34 // if level > current_level we call ourselves to go one level deeper
35 if level > current_level {
36 // Level can not be root level !!
37 // Add a sub-number to section
38 section.push(0);
39 let last = items
40 .pop()
41 .expect("There should be at least one item since this can't be the root level");
42
43 if let BookItem::Chapter(ref s, ref ch) = last {
44 let mut ch = ch.clone();
45 ch.sub_items = parse_level(summary, level, section.clone())?;
46 items.push(BookItem::Chapter(s.clone(), ch));
47
48 // Remove the last number from the section, because we got back to our level..
49 section.pop();
50 continue;
51 } else {
52 return Err(Error::new(ErrorKind::Other,
53 "Your summary.md is messed up\n\n
54 Prefix, \
55 Suffix and Spacer elements can only exist on the root level.\n
56 \
57 Prefix elements can only exist before any chapter and there can be \
58 no chapters after suffix elements."));
59 };
60
61 } else {
62 // level and current_level are the same, parse the line
63 item = if let Some(parsed_item) = parse_line(summary[0]) {
64
65 // Eliminate possible errors and set section to -1 after suffix
66 match parsed_item {
67 // error if level != 0 and BookItem is != Chapter
68 BookItem::Affix(_) |
69 BookItem::Spacer if level > 0 => {
70 return Err(Error::new(ErrorKind::Other,
71 "Your summary.md is messed up\n\n
72 \
73 Prefix, Suffix and Spacer elements can only exist on the \
74 root level.\n
75 Prefix \
76 elements can only exist before any chapter and there can be \
77 no chapters after suffix elements."))
78 },
79
80 // error if BookItem == Chapter and section == -1
81 BookItem::Chapter(_, _) if section[0] == -1 => {
82 return Err(Error::new(ErrorKind::Other,
83 "Your summary.md is messed up\n\n
84 \
85 Prefix, Suffix and Spacer elements can only exist on the \
86 root level.\n
87 Prefix \
88 elements can only exist before any chapter and there can be \
89 no chapters after suffix elements."))
90 },
91
92 // Set section = -1 after suffix
93 BookItem::Affix(_) if section[0] > 0 => {
94 section[0] = -1;
95 },
96
97 _ => {},
98 }
99
100 match parsed_item {
101 BookItem::Chapter(_, ch) => {
102 // Increment section
103 let len = section.len() - 1;
104 section[len] += 1;
105 let s = section
106 .iter()
107 .fold("".to_owned(), |s, i| s + &i.to_string() + ".");
108 BookItem::Chapter(s, ch)
109 },
110 _ => parsed_item,
111 }
112
113 } else {
114 // If parse_line does not return Some(_) continue...
115 summary.remove(0);
116 continue;
117 };
118 }
119
120 summary.remove(0);
121 items.push(item)
122 }
123 debug!("[*]: Level: {:?}", items);
124 Ok(items)
125 }
126
127
128 fn level(line: &str, spaces_in_tab: i32) -> Result<i32> {
129 debug!("[fn]: level");
130 let mut spaces = 0;
131 let mut level = 0;
132
133 for ch in line.chars() {
134 match ch {
135 ' ' => spaces += 1,
136 '\t' => level += 1,
137 _ => break,
138 }
139 if spaces >= spaces_in_tab {
140 level += 1;
141 spaces = 0;
142 }
143 }
144
145 // If there are spaces left, there is an indentation error
146 if spaces > 0 {
147 debug!("[SUMMARY.md]:");
148 debug!("\t[line]: {}", line);
149 debug!("[*]: There is an indentation error on this line. Indentation should be {} spaces", spaces_in_tab);
150 return Err(Error::new(ErrorKind::Other, format!("Indentation error on line:\n\n{}", line)));
151 }
152
153 Ok(level)
154 }
155
156
157 fn parse_line(l: &str) -> Option<BookItem> {
158 debug!("[fn]: parse_line");
159
160 // Remove leading and trailing spaces or tabs
161 let line = l.trim_matches(|c: char| c == ' ' || c == '\t');
162
163 // Spacers are "------"
164 if line.starts_with("--") {
165 debug!("[*]: Line is spacer");
166 return Some(BookItem::Spacer);
167 }
168
169 if let Some(c) = line.chars().nth(0) {
170 match c {
171 // List item
172 '-' | '*' => {
173 debug!("[*]: Line is list element");
174
175 if let Some((name, path)) = read_link(line) {
176 return Some(BookItem::Chapter("0".to_owned(), Chapter::new(name, path)));
177 } else {
178 return None;
179 }
180 },
181 // Non-list element
182 '[' => {
183 debug!("[*]: Line is a link element");
184
185 if let Some((name, path)) = read_link(line) {
186 return Some(BookItem::Affix(Chapter::new(name, path)));
187 } else {
188 return None;
189 }
190 },
191 _ => {},
192 }
193 }
194
195 None
196 }
197
198 fn read_link(line: &str) -> Option<(String, PathBuf)> {
199 let mut start_delimitor;
200 let mut end_delimitor;
201
202 // In the future, support for list item that is not a link
203 // Not sure if I should error on line I can't parse or just ignore them...
204 if let Some(i) = line.find('[') {
205 start_delimitor = i;
206 } else {
207 debug!("[*]: '[' not found, this line is not a link. Ignoring...");
208 return None;
209 }
210
211 if let Some(i) = line[start_delimitor..].find("](") {
212 end_delimitor = start_delimitor + i;
213 } else {
214 debug!("[*]: '](' not found, this line is not a link. Ignoring...");
215 return None;
216 }
217
218 let name = line[start_delimitor + 1..end_delimitor].to_owned();
219
220 start_delimitor = end_delimitor + 1;
221 if let Some(i) = line[start_delimitor..].find(')') {
222 end_delimitor = start_delimitor + i;
223 } else {
224 debug!("[*]: ')' not found, this line is not a link. Ignoring...");
225 return None;
226 }
227
228 let path = PathBuf::from(line[start_delimitor + 1..end_delimitor].to_owned());
229
230 Some((name, path))
231 }