extern crate mdbook;
extern crate regex;
extern crate failure;
use failure::Error;
use mdbook::renderer::RenderContext;
use mdbook::book::Chapter;
use mdbook::BookItem;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::io::Write;
use std::path::PathBuf;
use std::fs::{File, create_dir_all};
#[derive(Serialize, Deserialize, Debug, Eq)]
pub struct JSONChapter {
pub path: String,
pub title: String,
pub subchapters: Vec<JSONChapter>
}
impl PartialEq for JSONChapter {
fn eq(&self, other: &Self) -> bool {
self.path == other.path &&
self.title == other.title &&
self.subchapters == other.subchapters
}
}
#[derive(Serialize, Deserialize, Debug, Eq)]
pub struct JSONBook {
pub title: String,
pub chapters: Vec<JSONChapter>
}
impl PartialEq for JSONBook {
fn eq(&self, other: &Self) -> bool {
self.title == other.title && self.chapters == other.chapters
}
}
pub fn generate(context: &RenderContext) -> Result<(), Error> {
generate_html_json(context)?;
generate_markdown_contents(context)
}
fn generate_html_json(context: &RenderContext) -> Result<(), Error> {
let filename_regex = Regex::new(r"md$")?;
let chapters = context.book.sections.iter().filter_map(|item| {
if let BookItem::Chapter(ref chapter) = *item {
Some(parse_chapter(chapter, &filename_regex))
} else {
None
}
}).collect::<Vec<JSONChapter>>();
let title = &context.config.book.title.to_owned().unwrap_or(String::from(""));
write_book(title.to_string(), chapters, &context.destination, "book.json")?;
Ok(())
}
fn write_book(title: String, chapters: Vec<JSONChapter>, path: &PathBuf, filename: &str) -> Result<(), Error> {
let mut book_api = File::create(path.join(filename))?;
let book = JSONBook {
title,
chapters,
};
let content = serde_json::to_string(&book)?;
writeln!(book_api, "{}", content)?;
Ok(())
}
fn parse_chapter(chapter: &Chapter, filename_regex: &Regex) -> JSONChapter {
let chapter_path = chapter.path.to_str().expect("Chapter path not valid");
let path = String::from("/") + &filename_regex.replace_all(chapter_path, "html");
JSONChapter {
path,
title: chapter.name.clone(),
subchapters: chapter.sub_items.iter().filter_map(|item| {
if let BookItem::Chapter(ref chapter) = *item {
Some(parse_chapter(chapter, filename_regex))
} else {
None
}
}).collect()
}
}
fn generate_markdown_contents(context: &RenderContext) -> Result<(), Error> {
let chapters = context.book.sections.iter().filter_map(|item| {
if let BookItem::Chapter(ref chapter) = *item {
Some(copy_chapter(chapter, &context.destination))
} else {
None
}
}).collect::<Vec<JSONChapter>>();
let title = &context.config.book.title.to_owned().unwrap_or(String::from(""));
write_book(title.to_string(), chapters, &context.destination, "markdown.json")?;
Ok(())
}
fn copy_chapter(chapter: &Chapter, prefix: &PathBuf) -> JSONChapter {
let chapter_path = chapter.path.to_str().expect("Chapter path not valid");
let path = String::from("/api/markdown/") + chapter_path;
let file_path = prefix.join("markdown").join(chapter_path);
let parent_directory = file_path.parent().expect("");
create_dir_all(parent_directory).expect("Unable to create parent directory");
let mut chapter_file = File::create(&file_path).expect(&format!("Unable to create chapter file at {}", file_path.to_str().expect("")));
writeln!(chapter_file, "{}", chapter.content).expect("Unable to write chapter contents");
JSONChapter {
path,
title: chapter.name.clone(),
subchapters: chapter.sub_items.iter().filter_map(|item| {
if let BookItem::Chapter(ref chapter) = *item {
Some(copy_chapter(chapter, prefix))
} else {
None
}
}).collect()
}
}