2015-07-18 00:04:20 +02:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use std::fs::File;
|
|
|
|
|
use std::io::{Read, Result, Error, ErrorKind};
|
2015-09-11 20:52:55 +02:00
|
|
|
use book::bookitem::{BookItem, Chapter};
|
2015-07-18 00:04:20 +02:00
|
|
|
|
|
|
|
|
pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> {
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[fn]: construct_bookitems");
|
2015-07-18 00:04:20 +02:00
|
|
|
let mut summary = String::new();
|
|
|
|
|
try!(try!(File::open(path)).read_to_string(&mut summary));
|
|
|
|
|
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[*]: Parse SUMMARY.md");
|
2015-09-11 20:52:55 +02:00
|
|
|
let top_items = try!(parse_level(&mut summary.split('\n').collect(), 0, vec![0]));
|
|
|
|
|
debug!("[*]: Done parsing SUMMARY.md");
|
2015-07-18 00:04:20 +02:00
|
|
|
Ok(top_items)
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-11 20:52:55 +02:00
|
|
|
fn parse_level(summary: &mut Vec<&str>, current_level: i32, mut section: Vec<i32>) -> Result<Vec<BookItem>> {
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[fn]: parse_level");
|
2015-07-18 00:04:20 +02:00
|
|
|
let mut items: Vec<BookItem> = vec![];
|
|
|
|
|
|
2015-09-11 20:52:55 +02:00
|
|
|
// Construct the book recursively
|
2015-09-16 22:46:23 -04:00
|
|
|
while !summary.is_empty() {
|
2015-09-11 20:52:55 +02:00
|
|
|
let item: BookItem;
|
|
|
|
|
// Indentation level of the line to parse
|
2015-07-18 00:04:20 +02:00
|
|
|
let level = try!(level(summary[0], 4));
|
|
|
|
|
|
2015-09-11 20:52:55 +02:00
|
|
|
// if level < current_level we remove the last digit of section, exit the current function,
|
|
|
|
|
// and return the parsed level to the calling function.
|
2016-03-17 22:31:28 +01:00
|
|
|
if level < current_level {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-09-11 20:52:55 +02:00
|
|
|
|
|
|
|
|
// if level > current_level we call ourselves to go one level deeper
|
|
|
|
|
if level > current_level {
|
|
|
|
|
// Level can not be root level !!
|
|
|
|
|
// Add a sub-number to section
|
2015-09-18 16:18:37 +02:00
|
|
|
section.push(0);
|
2015-09-11 20:52:55 +02:00
|
|
|
let last = items.pop().expect("There should be at least one item since this can't be the root level");
|
|
|
|
|
|
2016-12-31 10:39:48 -08:00
|
|
|
if let BookItem::Chapter(ref s, ref ch) = last {
|
2015-09-11 20:52:55 +02:00
|
|
|
let mut ch = ch.clone();
|
|
|
|
|
ch.sub_items = try!(parse_level(summary, level, section.clone()));
|
|
|
|
|
items.push(BookItem::Chapter(s.clone(), ch));
|
|
|
|
|
|
|
|
|
|
// Remove the last number from the section, because we got back to our level..
|
|
|
|
|
section.pop();
|
2016-03-17 22:31:28 +01:00
|
|
|
continue;
|
2015-09-11 20:52:55 +02:00
|
|
|
} else {
|
2016-03-17 22:31:28 +01:00
|
|
|
return Err(Error::new(ErrorKind::Other,
|
|
|
|
|
format!("Your summary.md is messed up\n\n
|
|
|
|
|
Prefix, \
|
|
|
|
|
Suffix and Spacer elements can only exist on the root level.\n
|
|
|
|
|
\
|
|
|
|
|
Prefix elements can only exist before any chapter and there can be \
|
|
|
|
|
no chapters after suffix elements.")));
|
2015-09-11 20:52:55 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// level and current_level are the same, parse the line
|
|
|
|
|
item = if let Some(parsed_item) = parse_line(summary[0]) {
|
|
|
|
|
|
|
|
|
|
// Eliminate possible errors and set section to -1 after suffix
|
|
|
|
|
match parsed_item {
|
|
|
|
|
// error if level != 0 and BookItem is != Chapter
|
|
|
|
|
BookItem::Affix(_) | BookItem::Spacer if level > 0 => {
|
2016-03-17 22:31:28 +01:00
|
|
|
return Err(Error::new(ErrorKind::Other,
|
|
|
|
|
format!("Your summary.md is messed up\n\n
|
|
|
|
|
\
|
|
|
|
|
Prefix, Suffix and Spacer elements can only exist on the \
|
|
|
|
|
root level.\n
|
|
|
|
|
Prefix \
|
|
|
|
|
elements can only exist before any chapter and there can be \
|
|
|
|
|
no chapters after suffix elements.")))
|
2015-09-11 20:52:55 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// error if BookItem == Chapter and section == -1
|
|
|
|
|
BookItem::Chapter(_, _) if section[0] == -1 => {
|
2016-03-17 22:31:28 +01:00
|
|
|
return Err(Error::new(ErrorKind::Other,
|
|
|
|
|
format!("Your summary.md is messed up\n\n
|
|
|
|
|
\
|
|
|
|
|
Prefix, Suffix and Spacer elements can only exist on the \
|
|
|
|
|
root level.\n
|
|
|
|
|
Prefix \
|
|
|
|
|
elements can only exist before any chapter and there can be \
|
|
|
|
|
no chapters after suffix elements.")))
|
2015-09-11 20:52:55 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Set section = -1 after suffix
|
|
|
|
|
BookItem::Affix(_) if section[0] > 0 => {
|
|
|
|
|
section[0] = -1;
|
2016-03-17 22:31:28 +01:00
|
|
|
},
|
2015-09-11 20:52:55 +02:00
|
|
|
|
|
|
|
|
_ => {},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match parsed_item {
|
|
|
|
|
BookItem::Chapter(_, ch) => {
|
|
|
|
|
// Increment section
|
2016-03-17 22:31:28 +01:00
|
|
|
let len = section.len() - 1;
|
2015-09-11 20:52:55 +02:00
|
|
|
section[len] += 1;
|
|
|
|
|
let s = section.iter().fold("".to_owned(), |s, i| s + &i.to_string() + ".");
|
|
|
|
|
BookItem::Chapter(s, ch)
|
2016-03-17 22:31:28 +01:00
|
|
|
},
|
|
|
|
|
_ => parsed_item,
|
2015-09-11 20:52:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
// If parse_line does not return Some(_) continue...
|
|
|
|
|
summary.remove(0);
|
|
|
|
|
continue;
|
|
|
|
|
};
|
2015-07-18 00:04:20 +02:00
|
|
|
}
|
|
|
|
|
|
2015-09-11 20:52:55 +02:00
|
|
|
summary.remove(0);
|
|
|
|
|
items.push(item)
|
|
|
|
|
}
|
|
|
|
|
debug!("[*]: Level: {:?}", items);
|
2015-07-18 00:04:20 +02:00
|
|
|
Ok(items)
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-11 20:52:55 +02:00
|
|
|
|
2015-07-18 00:04:20 +02:00
|
|
|
fn level(line: &str, spaces_in_tab: i32) -> Result<i32> {
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[fn]: level");
|
2015-07-18 00:04:20 +02:00
|
|
|
let mut spaces = 0;
|
|
|
|
|
let mut level = 0;
|
|
|
|
|
|
|
|
|
|
for ch in line.chars() {
|
|
|
|
|
match ch {
|
|
|
|
|
' ' => spaces += 1,
|
|
|
|
|
'\t' => level += 1,
|
|
|
|
|
_ => break,
|
|
|
|
|
}
|
|
|
|
|
if spaces >= spaces_in_tab {
|
|
|
|
|
level += 1;
|
|
|
|
|
spaces = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there are spaces left, there is an indentation error
|
|
|
|
|
if spaces > 0 {
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[SUMMARY.md]:");
|
|
|
|
|
debug!("\t[line]: {}", line);
|
|
|
|
|
debug!("[*]: There is an indentation error on this line. Indentation should be {} spaces", spaces_in_tab);
|
2016-03-17 22:31:28 +01:00
|
|
|
return Err(Error::new(ErrorKind::Other, format!("Indentation error on line:\n\n{}", line)));
|
2015-07-18 00:04:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(level)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-07-18 16:21:04 +02:00
|
|
|
fn parse_line(l: &str) -> Option<BookItem> {
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[fn]: parse_line");
|
2015-09-11 20:52:55 +02:00
|
|
|
|
2015-07-18 00:04:20 +02:00
|
|
|
// Remove leading and trailing spaces or tabs
|
2016-03-17 22:31:28 +01:00
|
|
|
let line = l.trim_matches(|c: char| c == ' ' || c == '\t');
|
2015-07-18 00:04:20 +02:00
|
|
|
|
2015-09-11 20:52:55 +02:00
|
|
|
// Spacers are "------"
|
|
|
|
|
if line.starts_with("--") {
|
|
|
|
|
debug!("[*]: Line is spacer");
|
2016-03-17 22:31:28 +01:00
|
|
|
return Some(BookItem::Spacer);
|
2015-09-11 20:52:55 +02:00
|
|
|
}
|
|
|
|
|
|
2015-07-18 00:04:20 +02:00
|
|
|
if let Some(c) = line.chars().nth(0) {
|
|
|
|
|
match c {
|
|
|
|
|
// List item
|
|
|
|
|
'-' | '*' => {
|
2015-08-03 18:06:01 +02:00
|
|
|
debug!("[*]: Line is list element");
|
2015-07-18 00:04:20 +02:00
|
|
|
|
2015-09-11 20:52:55 +02:00
|
|
|
if let Some((name, path)) = read_link(line) {
|
2016-03-17 22:31:28 +01:00
|
|
|
return Some(BookItem::Chapter("0".to_owned(), Chapter::new(name, path)));
|
|
|
|
|
} else {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2015-09-11 20:52:55 +02:00
|
|
|
},
|
|
|
|
|
// Non-list element
|
|
|
|
|
'[' => {
|
|
|
|
|
debug!("[*]: Line is a link element");
|
|
|
|
|
|
|
|
|
|
if let Some((name, path)) = read_link(line) {
|
2016-03-17 22:31:28 +01:00
|
|
|
return Some(BookItem::Affix(Chapter::new(name, path)));
|
|
|
|
|
} else {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
_ => {},
|
2015-07-18 00:04:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
2015-09-11 20:52:55 +02:00
|
|
|
|
|
|
|
|
fn read_link(line: &str) -> Option<(String, PathBuf)> {
|
|
|
|
|
let mut start_delimitor;
|
|
|
|
|
let mut end_delimitor;
|
|
|
|
|
|
|
|
|
|
// In the future, support for list item that is not a link
|
|
|
|
|
// Not sure if I should error on line I can't parse or just ignore them...
|
2016-03-17 22:31:28 +01:00
|
|
|
if let Some(i) = line.find('[') {
|
|
|
|
|
start_delimitor = i;
|
|
|
|
|
} else {
|
2015-09-11 20:52:55 +02:00
|
|
|
debug!("[*]: '[' not found, this line is not a link. Ignoring...");
|
2016-03-17 22:31:28 +01:00
|
|
|
return None;
|
2015-09-11 20:52:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(i) = line[start_delimitor..].find("](") {
|
2016-03-17 22:31:28 +01:00
|
|
|
end_delimitor = start_delimitor + i;
|
|
|
|
|
} else {
|
2015-09-11 20:52:55 +02:00
|
|
|
debug!("[*]: '](' not found, this line is not a link. Ignoring...");
|
2016-03-17 22:31:28 +01:00
|
|
|
return None;
|
2015-09-11 20:52:55 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-17 22:31:28 +01:00
|
|
|
let name = line[start_delimitor + 1..end_delimitor].to_owned();
|
2015-09-11 20:52:55 +02:00
|
|
|
|
|
|
|
|
start_delimitor = end_delimitor + 1;
|
|
|
|
|
if let Some(i) = line[start_delimitor..].find(')') {
|
|
|
|
|
end_delimitor = start_delimitor + i;
|
2016-03-17 22:31:28 +01:00
|
|
|
} else {
|
2015-09-11 20:52:55 +02:00
|
|
|
debug!("[*]: ')' not found, this line is not a link. Ignoring...");
|
2016-03-17 22:31:28 +01:00
|
|
|
return None;
|
2015-09-11 20:52:55 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-17 22:31:28 +01:00
|
|
|
let path = PathBuf::from(line[start_delimitor + 1..end_delimitor].to_owned());
|
2015-09-11 20:52:55 +02:00
|
|
|
|
|
|
|
|
Some((name, path))
|
|
|
|
|
}
|