use anyhow::{Context, Result};
use mdbook::book::{Link, SectionNumber, Summary, SummaryItem};
use mdbook::preprocess::PreprocessorContext;
use mdbook::MDBook;
use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
pub fn load_book(ctx: &PreprocessorContext) -> Result<MDBook> {
let summary = load_summary(ctx.config.book.src.as_path())?;
let book =
MDBook::load_with_config_and_summary(ctx.root.as_path(), ctx.config.clone(), summary)?;
Ok(book)
}
fn load_summary(book_src: &Path) -> Result<Summary> {
let mut numbered_chapters = load_summary_items(book_src, book_src)?;
apply_section_numbers(&mut numbered_chapters, &Vec::default());
Ok(Summary {
title: None,
prefix_chapters: Default::default(),
numbered_chapters,
suffix_chapters: Default::default(),
})
}
fn apply_section_numbers(chapters: &mut [SummaryItem], parent_num: &Vec<u32>) {
let mut i = 0_u32;
for chapter in chapters {
i += 1;
if let SummaryItem::Link(ref mut link) = *chapter {
let mut num = parent_num.clone();
num.push(i);
apply_section_numbers(&mut link.nested_items, &num);
link.number = Some(SectionNumber(num));
}
}
}
fn load_summary_items<P: AsRef<Path>>(path: P, book_src: &Path) -> Result<Vec<SummaryItem>> {
let mut map = BTreeMap::default();
let summary_path: PathBuf = [PathBuf::from(book_src), PathBuf::from("SUMMARY.md")]
.iter()
.collect();
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path().to_path_buf();
if path == summary_path {
continue;
}
if let Some(item) = load_summary_item(entry, book_src)? {
map.insert(path, item);
}
}
Ok(map.values().cloned().collect())
}
fn load_summary_item(entry: fs::DirEntry, book_src: &Path) -> Result<Option<SummaryItem>> {
let ft = entry.file_type()?;
if ft.is_dir() {
let nested_items = load_summary_items(entry.path(), book_src)?;
let index_file: PathBuf = [entry.path(), PathBuf::from("00.md")].iter().collect();
if !index_file.exists() {
if nested_items.is_empty() {
return Ok(None);
}
return Err(anyhow::Error::msg(format!(
"missing folder index file: {}",
index_file.display()
)));
}
let location = index_file.strip_prefix(book_src)?;
return Ok(Some(SummaryItem::Link(Link {
name: load_summary_title(index_file.as_path())?,
location: Some(location.to_path_buf()),
number: None, nested_items,
})));
}
if ft.is_file() {
let os_filename = entry.file_name();
let filename = os_filename.to_string_lossy();
if filename == "00.md" {
return Ok(None);
}
if filename.starts_with("_") {
return Ok(None);
}
if !filename.ends_with(".md") {
return Ok(None);
}
let path = entry.path().to_path_buf();
let location = path.strip_prefix(book_src)?;
return Ok(Some(SummaryItem::Link(Link {
name: load_summary_title(entry.path())?,
location: Some(location.to_path_buf()),
number: None, nested_items: Default::default(),
})));
}
Ok(None)
}
fn load_summary_title<P: AsRef<Path>>(path: P) -> Result<String> {
let contents = fs::read_to_string(path.as_ref())
.with_context(|| format!("could not read file: {}", path.as_ref().display()))?;
let mut title = None;
for l in contents.lines() {
if l.starts_with("# ") {
if let Some(t) = l.get(2..) {
title = Some(String::from(t))
}
break;
}
}
title.ok_or_else(|| {
anyhow::Error::msg(format!(
"could not find H1 title from: {}",
path.as_ref().display()
))
})
}