blob: 36a37dba1fa0f7cf4df405b9d520aa7057551805 [file] [log] [blame]
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Basic data structures for representing a book.
use std::io::prelude::*;
use std::io::BufReader;
use std::iter;
use std::path::{Path, PathBuf};
pub struct BookItem {
pub title: String,
pub path: PathBuf,
pub path_to_root: PathBuf,
pub children: Vec<BookItem>,
}
pub struct Book {
pub chapters: Vec<BookItem>,
}
/// A depth-first iterator over a book.
pub struct BookItems<'a> {
cur_items: &'a [BookItem],
cur_idx: usize,
stack: Vec<(&'a [BookItem], usize)>,
}
impl<'a> Iterator for BookItems<'a> {
type Item = (String, &'a BookItem);
fn next(&mut self) -> Option<(String, &'a BookItem)> {
loop {
if self.cur_idx >= self.cur_items.len() {
match self.stack.pop() {
None => return None,
Some((parent_items, parent_idx)) => {
self.cur_items = parent_items;
self.cur_idx = parent_idx + 1;
}
}
} else {
let cur = self.cur_items.get(self.cur_idx).unwrap();
let mut section = "".to_string();
for &(_, idx) in &self.stack {
section.push_str(&(idx + 1).to_string()[..]);
section.push('.');
}
section.push_str(&(self.cur_idx + 1).to_string()[..]);
section.push('.');
self.stack.push((self.cur_items, self.cur_idx));
self.cur_items = &cur.children[..];
self.cur_idx = 0;
return Some((section, cur))
}
}
}
}
impl Book {
pub fn iter(&self) -> BookItems {
BookItems {
cur_items: &self.chapters[..],
cur_idx: 0,
stack: Vec::new(),
}
}
}
/// Construct a book by parsing a summary (markdown table of contents).
pub fn parse_summary(input: &mut Read, src: &Path) -> Result<Book, Vec<String>> {
fn collapse(stack: &mut Vec<BookItem>,
top_items: &mut Vec<BookItem>,
to_level: usize) {
loop {
if stack.len() < to_level { return }
if stack.len() == 1 {
top_items.push(stack.pop().unwrap());
return;
}
let tip = stack.pop().unwrap();
let last = stack.len() - 1;
stack[last].children.push(tip);
}
}
let mut top_items = vec!();
let mut stack = vec!();
let mut errors = vec!();
// always include the introduction
top_items.push(BookItem {
title: "Introduction".to_string(),
path: PathBuf::from("README.md"),
path_to_root: PathBuf::from(""),
children: vec!(),
});
for line_result in BufReader::new(input).lines() {
let line = match line_result {
Ok(line) => line,
Err(err) => {
errors.push(err.to_string());
return Err(errors);
}
};
let star_idx = match line.find("*") { Some(i) => i, None => continue };
let start_bracket = star_idx + line[star_idx..].find("[").unwrap();
let end_bracket = start_bracket + line[start_bracket..].find("](").unwrap();
let start_paren = end_bracket + 1;
let end_paren = start_paren + line[start_paren..].find(")").unwrap();
let given_path = &line[start_paren + 1 .. end_paren];
let title = line[start_bracket + 1..end_bracket].to_string();
let indent = &line[..star_idx];
let path_from_root = match src.join(given_path).strip_prefix(src) {
Ok(p) => p.to_path_buf(),
Err(..) => {
errors.push(format!("paths in SUMMARY.md must be relative, \
but path '{}' for section '{}' is not.",
given_path, title));
PathBuf::new()
}
};
let path_to_root = PathBuf::from(&iter::repeat("../")
.take(path_from_root.components().count() - 1)
.collect::<String>());
let item = BookItem {
title: title,
path: path_from_root,
path_to_root: path_to_root,
children: vec!(),
};
let level = indent.chars().map(|c| -> usize {
match c {
' ' => 1,
'\t' => 4,
_ => unreachable!()
}
}).sum::<usize>() / 4 + 1;
if level > stack.len() + 1 {
errors.push(format!("section '{}' is indented too deeply; \
found {}, expected {} or less",
item.title, level, stack.len() + 1));
} else if level <= stack.len() {
collapse(&mut stack, &mut top_items, level);
}
stack.push(item)
}
if errors.is_empty() {
collapse(&mut stack, &mut top_items, 1);
Ok(Book { chapters: top_items })
} else {
Err(errors)
}
}