mdbook_lad_preprocessor/
lib.rs

1//! The library crate for the mdbook LAD preprocessor.
2#![allow(missing_docs)]
3
4use mdbook::{errors::Error, preprocess::Preprocessor};
5use sections::Section;
6mod argument_visitor;
7mod markdown;
8mod sections;
9
10const LAD_EXTENSION: &str = "lad.json";
11
12pub struct LADPreprocessor;
13
14impl LADPreprocessor {
15    /// Checks if a chapter is a LAD file.
16    fn is_lad_file(chapter: &mdbook::book::Chapter) -> bool {
17        chapter
18            .source_path
19            .as_ref()
20            .and_then(|a| a.file_name())
21            .map(|s| s.to_string_lossy().ends_with(LAD_EXTENSION))
22            .unwrap_or(false)
23    }
24
25    /// Process a chapter that is a LAD file.
26    ///
27    /// `parent` is the optional parent chapter reference,
28    /// and `chapter_index` is the index of the chapter among its siblings.
29    fn process_lad_chapter(
30        chapter: &mdbook::book::Chapter,
31        parent: Option<&mdbook::book::Chapter>,
32        chapter_index: usize,
33    ) -> Result<mdbook::book::Chapter, Error> {
34        let chapter_title = chapter.name.trim_end_matches(".lad.json").to_owned();
35        let ladfile = ladfile::parse_lad_file(&chapter.content)
36            .map_err(|e| Error::new(e).context("Failed to parse LAD file"))?;
37        log::debug!(
38            "Parsed LAD file: {}",
39            serde_json::to_string_pretty(&ladfile).unwrap_or_default()
40        );
41        let new_chapter = Section::Summary {
42            ladfile: &ladfile,
43            title: Some(chapter_title),
44        }
45        .into_chapter(parent, chapter_index);
46        log::debug!(
47            "New chapter: {}",
48            serde_json::to_string_pretty(&new_chapter).unwrap_or_default()
49        );
50        Ok(new_chapter)
51    }
52}
53
54impl Preprocessor for LADPreprocessor {
55    fn name(&self) -> &str {
56        "lad-preprocessor"
57    }
58
59    fn run(
60        &self,
61        _ctx: &mdbook::preprocess::PreprocessorContext,
62        mut book: mdbook::book::Book,
63    ) -> mdbook::errors::Result<mdbook::book::Book> {
64        let mut errors = Vec::new();
65
66        // first replace children in parents
67        book.for_each_mut(|item| {
68            if let mdbook::BookItem::Chapter(parent) = item {
69                // First, collect the indices and new chapters for LAD file chapters.
70                let replacements: Vec<(usize, mdbook::book::Chapter)> = parent
71                    .sub_items
72                    .iter()
73                    .enumerate()
74                    .filter_map(|(idx, item)| {
75                        if let mdbook::BookItem::Chapter(chapter) = item {
76                            if LADPreprocessor::is_lad_file(chapter) {
77                                match LADPreprocessor::process_lad_chapter(
78                                    chapter,
79                                    Some(parent),
80                                    idx,
81                                ) {
82                                    Ok(new_chapter) => return Some((idx, new_chapter)),
83                                    Err(e) => {
84                                        errors.push(e);
85                                        return None;
86                                    }
87                                }
88                            }
89                        }
90                        None
91                    })
92                    .collect();
93
94                // Then, apply the replacements.
95                for (idx, new_chapter) in replacements {
96                    if let mdbook::BookItem::Chapter(chapter) = &mut parent.sub_items[idx] {
97                        *chapter = new_chapter;
98                    }
99                }
100            }
101        });
102
103        // then try match items themselves
104        book.for_each_mut(|item| {
105            if let mdbook::BookItem::Chapter(chapter) = item {
106                if !LADPreprocessor::is_lad_file(chapter) {
107                    return;
108                }
109
110                let new_chapter = match LADPreprocessor::process_lad_chapter(chapter, None, 0) {
111                    Ok(new_chapter) => new_chapter,
112                    Err(e) => {
113                        errors.push(e);
114                        return;
115                    }
116                };
117
118                *chapter = new_chapter;
119            }
120        });
121
122        log::debug!(
123            "Book after LAD processing: {}",
124            serde_json::to_string_pretty(&book).unwrap_or_default()
125        );
126
127        if !errors.is_empty() {
128            // return on first error
129            for error in errors {
130                log::error!("{}", error);
131                Err(error)?;
132            }
133        }
134
135        Ok(book)
136    }
137}