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
).strip_prefix(src
) {
130 Ok(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 }
)