Rudimentary: Parse SUMMARY.md, support for nested levels. Only list items: "- [name](path)" or "* [name](path)" #2

This commit is contained in:
Mathieu David 2015-07-18 00:04:20 +02:00
parent 4fe0bc2de5
commit 7fa5b06ccb
8 changed files with 187 additions and 14 deletions

View file

@ -3,6 +3,7 @@ use std::path::PathBuf;
pub struct BookConfig {
dest: PathBuf,
src: PathBuf,
indent_spaces: i32,
multilingual: bool,
}
@ -12,6 +13,7 @@ impl BookConfig {
BookConfig {
dest: PathBuf::from("book"),
src: PathBuf::from("src"),
indent_spaces: 4,
multilingual: false,
}
}

View file

@ -1,21 +1,41 @@
use std::path::PathBuf;
pub struct BookItem {
name: String,
path: PathBuf,
sub_items: Vec<BookItem>,
pub name: String,
pub path: PathBuf,
pub sub_items: Vec<BookItem>,
spacer: bool,
}
pub enum ItemType {
Pre,
Chapter,
Post
}
impl BookItem {
fn new(name: String, path: PathBuf) -> Self {
pub fn new(name: String, path: PathBuf) -> Self {
BookItem {
name: name,
path: path,
sub_items: vec![],
spacer: false,
}
}
pub fn spacer() -> Self {
BookItem {
name: String::from("SPACER"),
path: PathBuf::new(),
sub_items: vec![],
spacer: true,
}
}
fn push(&mut self, item: BookItem) {
self.sub_items.push(item);
}
}

View file

@ -1,10 +1,10 @@
use std::path::PathBuf;
use std::fs::{self, File, metadata};
use std::io::Write;
use std::io::Error;
use std::io::{Write, Result};
use book::bookconfig::BookConfig;
use book::bookitem::BookItem;
use parse;
pub struct MDBook {
title: String,
@ -37,7 +37,7 @@ impl MDBook {
}
}
pub fn init(&self) -> Result<(), Error> {
pub fn init(&self) -> Result<()> {
let dest = self.config.dest();
let src = self.config.src();
@ -67,11 +67,11 @@ impl MDBook {
Err(_) => {
// There is a very high chance that the error is due to the fact that
// the directory / file does not exist
Result::Ok(File::create(&src.join("SUMMARY.md")).unwrap())
Ok(File::create(&src.join("SUMMARY.md")).unwrap())
},
Ok(_) => {
/* If there is no error, the directory / file does exist */
Result::Err("SUMMARY.md does already exist")
Err("SUMMARY.md does already exist")
}
};
@ -87,9 +87,9 @@ impl MDBook {
return Ok(());
}
pub fn build(&self) -> Result<(), Error> {
pub fn build(&mut self) -> Result<()> {
try!(self.parse_summary());
Ok(())
}
@ -116,4 +116,23 @@ impl MDBook {
self
}
// Construct book
fn parse_summary(&mut self) -> Result<()> {
// When append becomes stale, use self.content.append() ...
let book_items = try!(parse::construct_bookitems(&self.config.src().join("SUMMARY.md")));
for item in book_items {
self.content.push(item)
}
// Debug
for item in &self.content {
println!("name: \"{}\" path: {:?}", item.name, item.path);
}
Ok(())
}
}

View file

@ -1,6 +1,7 @@
pub mod mdbook;
pub mod bookitem;
mod bookconfig;
mod bookitem;
use self::bookconfig::BookConfig;
use self::bookitem::BookItem;

View file

@ -1,3 +1,5 @@
mod book;
mod parse;
pub use book::mdbook::MDBook;
use book::bookitem::BookItem;

View file

@ -143,8 +143,13 @@ fn build(args: Vec<String>) {
println!("{}", usage);
}
let dir = std::env::current_dir().unwrap();
let book = MDBook::new(&dir);
let dir = if args.len() <= 2 {
std::env::current_dir().unwrap()
} else {
std::env::current_dir().unwrap().join(&args[2])
};
let mut book = MDBook::new(&dir);
if let Err(e) = book.build() {
println!("Error: {}", e);

3
src/parse/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub use self::summary::construct_bookitems;
pub mod summary;

121
src/parse/summary.rs Normal file
View file

@ -0,0 +1,121 @@
use std::path::PathBuf;
use std::fs::File;
use std::io::{Read, Result, Error, ErrorKind};
use book::bookitem::BookItem;
/*
pub enum LineType {
Blank,
Header,
Link(String, PathBuf), // Name, Path
ListItem(String, PathBuf, i32), // Name, Path, Level
Other,
}
*/
pub fn construct_bookitems(path: &PathBuf) -> Result<Vec<BookItem>> {
let mut summary = String::new();
try!(try!(File::open(path)).read_to_string(&mut summary));
let top_items = try!(parse_level(&mut summary.split('\n').collect(), 0));
Ok(top_items)
}
fn parse_level(summary: &mut Vec<&str>, current_level: i32) -> Result<Vec<BookItem>> {
let mut items: Vec<BookItem> = vec![];
loop {
if summary.len() <= 0 { break }
let level = try!(level(summary[0], 4));
if current_level > level { break }
else if current_level < level {
items.last_mut().unwrap().sub_items = try!(parse_level(summary, level))
}
else {
// Do the thing
if let Some(item) = parse_line(summary[0].clone()) {
items.push(item);
}
summary.remove(0);
}
}
Ok(items)
}
fn level(line: &str, spaces_in_tab: i32) -> Result<i32> {
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 {
return Err(Error::new(
ErrorKind::Other,
format!("There is an indentation error on line:\n\n{}", line)
)
)
}
Ok(level)
}
fn parse_line(line: &str) -> Option<BookItem> {
let mut name;
let mut path;
// Remove leading and trailing spaces or tabs
line.trim_matches(|c: char| { c == ' ' || c == '\t' });
if let Some(c) = line.chars().nth(0) {
match c {
// List item
'-' | '*' => {
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...
if let Some(i) = line.find('[') { start_delimitor = i; }
else { return None }
if let Some(i) = line[start_delimitor..].find("](") {
end_delimitor = start_delimitor +i;
}
else { return None }
name = line[start_delimitor + 1 .. end_delimitor].to_string();
start_delimitor = end_delimitor + 1;
if let Some(i) = line[start_delimitor..].find(')') {
end_delimitor = start_delimitor + i;
}
else { return None }
path = PathBuf::from(line[start_delimitor + 1 .. end_delimitor].to_string());
return Some(BookItem::new(name, path))
}
_ => {}
}
}
None
}