just/bin/generate-book/src/main.rs

191 lines
4.8 KiB
Rust
Raw Normal View History

2022-05-03 23:05:55 -07:00
use {
2022-06-12 18:02:09 -07:00
pulldown_cmark::{CowStr, Event, HeadingLevel, Options, Parser, Tag},
2022-05-03 23:05:55 -07:00
pulldown_cmark_to_cmark::cmark,
2022-09-11 02:25:38 -07:00
std::{collections::BTreeMap, error::Error, fmt::Write, fs, ops::Deref},
2022-05-03 23:05:55 -07:00
};
2022-06-12 18:02:09 -07:00
type Result<T = ()> = std::result::Result<T, Box<dyn Error>>;
#[derive(Copy, Clone, Debug)]
enum Language {
English,
Chinese,
}
impl Language {
fn code(&self) -> &'static str {
match self {
Self::English => "en",
Self::Chinese => "zh",
}
}
fn suffix(&self) -> &'static str {
match self {
Self::English => "",
Self::Chinese => ".中文",
}
}
fn introduction(&self) -> &'static str {
match self {
Self::Chinese => "说明",
Self::English => "Introduction",
}
}
}
2022-06-12 18:02:09 -07:00
#[derive(Debug)]
struct Chapter<'a> {
level: HeadingLevel,
events: Vec<Event<'a>>,
index: usize,
language: Language,
}
impl<'a> Chapter<'a> {
fn title(&self) -> String {
if self.index == 0 {
return self.language.introduction().into();
}
self
.events
.iter()
.skip_while(|event| !matches!(event, Event::Start(Tag::Heading(..))))
.skip(1)
.take_while(|event| !matches!(event, Event::End(Tag::Heading(..))))
.filter_map(|event| match event {
Event::Code(content) | Event::Text(content) => Some(content.deref()),
_ => None,
})
.collect()
}
fn number(&self) -> usize {
self.index + 1
}
fn markdown(&self) -> Result<String> {
let mut markdown = String::new();
cmark(self.events.iter(), &mut markdown)?;
if self.index == 0 {
markdown = markdown.split_inclusive('\n').skip(1).collect::<String>();
}
Ok(markdown)
}
}
fn slug(s: &str) -> String {
let mut slug = String::new();
for c in s.chars() {
match c {
'A'..='Z' => slug.extend(c.to_lowercase()),
' ' => slug.push('-'),
'?' | '.' | '' => {}
_ => slug.push(c),
}
}
slug
}
fn main() -> Result {
for language in [Language::English, Language::Chinese] {
let src = format!("book/{}/src", language.code());
fs::remove_dir_all(&src).ok();
fs::create_dir(&src)?;
2022-05-03 23:05:55 -07:00
let txt = fs::read_to_string(format!("README{}.md", language.suffix()))?;
2022-05-03 23:05:55 -07:00
2022-06-12 18:02:09 -07:00
let mut chapters = vec![Chapter {
level: HeadingLevel::H1,
events: Vec::new(),
index: 0,
language,
}];
2022-05-03 23:05:55 -07:00
for event in Parser::new_ext(&txt, Options::all()) {
2022-06-12 18:02:09 -07:00
if let Event::Start(Tag::Heading(level @ (HeadingLevel::H2 | HeadingLevel::H3), ..)) = event {
let index = chapters.last().unwrap().index + 1;
chapters.push(Chapter {
level,
events: Vec::new(),
index,
language,
});
}
chapters.last_mut().unwrap().events.push(event);
}
let mut links = BTreeMap::new();
for chapter in &chapters {
let mut current = None;
for event in &chapter.events {
match event {
Event::Start(Tag::Heading(..)) => current = Some(Vec::new()),
Event::End(Tag::Heading(level, ..)) => {
let events = current.unwrap();
let title = events
.iter()
.filter_map(|event| match event {
Event::Code(content) | Event::Text(content) => Some(content.deref()),
_ => None,
})
.collect::<String>();
let slug = slug(&title);
let link = if let HeadingLevel::H1 | HeadingLevel::H2 | HeadingLevel::H3 = level {
format!("chapter_{}.html", chapter.number())
} else {
format!("chapter_{}.html#{}", chapter.number(), slug)
};
links.insert(slug, link);
current = None;
}
_ => {
if let Some(events) = &mut current {
events.push(event.clone());
}
}
}
}
}
for chapter in &mut chapters {
for event in &mut chapter.events {
if let Event::Start(Tag::Link(_, dest, _)) | Event::End(Tag::Link(_, dest, _)) = event {
if let Some(anchor) = dest.clone().strip_prefix('#') {
*dest = CowStr::Borrowed(&links[anchor]);
}
}
}
2022-05-03 23:05:55 -07:00
}
let mut summary = String::new();
2022-06-12 18:02:09 -07:00
for chapter in chapters {
let path = format!("{}/chapter_{}.md", src, chapter.number());
2022-12-15 16:53:21 -08:00
fs::write(path, chapter.markdown()?)?;
2022-06-12 18:02:09 -07:00
let indent = match chapter.level {
HeadingLevel::H1 => 0,
HeadingLevel::H2 => 1,
HeadingLevel::H3 => 2,
HeadingLevel::H4 => 3,
HeadingLevel::H5 => 4,
HeadingLevel::H6 => 5,
};
2022-09-11 02:25:38 -07:00
writeln!(
summary,
"{}- [{}](chapter_{}.md)",
2022-06-12 18:02:09 -07:00
" ".repeat(indent * 4),
chapter.title(),
chapter.number()
2022-09-11 02:25:38 -07:00
)?;
}
fs::write(format!("{src}/SUMMARY.md"), summary).unwrap();
}
2022-05-03 23:05:55 -07:00
Ok(())
}