mdbook_selfpath/
lib.rs

1use mdbook::BookItem;
2use mdbook::book::Book;
3use mdbook::errors::Error;
4use mdbook::preprocess::{Preprocessor, PreprocessorContext};
5use regex::Regex;
6use std::path::Path;
7use std::path::PathBuf;
8
9#[derive(Default)]
10pub struct SelfPathPreprocessor;
11
12impl SelfPathPreprocessor {
13    pub fn new() -> SelfPathPreprocessor {
14        SelfPathPreprocessor
15    }
16}
17
18impl Preprocessor for SelfPathPreprocessor {
19    fn name(&self) -> &str {
20        "selfpath"
21    }
22
23    fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
24        // Determine the include-file-ext setting from [preprocessor.selfpath] (default true)
25        let include_ext = ctx
26            .config
27            .get_preprocessor(self.name())
28            .and_then(|table| table.get("include-file-ext"))
29            .and_then(|val| val.as_bool())
30            .unwrap_or(true);
31
32        // Determine the source directory name (relative to book root) from config or default "src"
33        let src_dir_name = &ctx.config.book.src;
34
35        // Regex to match {{#selfpath}}, {{ #selfpath }}, etc.
36        let selfpath_re = Regex::new(r"\{\{\s*selfpath\s*\}\}").unwrap();
37
38        // Iterate through all chapters in the book (including subchapters)
39        book.for_each_mut(|item: &mut BookItem| {
40            if let BookItem::Chapter(chapter) = item {
41                // Only process real chapters that have a source path
42                if let Some(ref source_path) = chapter.source_path {
43                    let mut rel_path = PathBuf::new();
44                    // push book src path only if it's not "."
45                    if src_dir_name.as_path() != Path::new(".") {
46                        rel_path.push(&src_dir_name)
47                    }
48                    rel_path.push(source_path);
49                    if !include_ext {
50                        // Remove the .md extension if present
51                        rel_path.set_extension("");
52                    }
53                    // Convert path to a forward-slash string (for consistency across OS)
54                    let rel_path_str = rel_path.to_string_lossy().replace('\\', "/");
55                    // Perform the replacement in the chapter content using regex
56                    chapter.content = selfpath_re.replace_all(&chapter.content, rel_path_str.as_str()).to_string();
57                }
58            }
59        });
60        Ok(book)
61    }
62
63    fn supports_renderer(&self, _renderer: &str) -> bool {
64        // This is not strictly used in our setup, since main.rs handles "supports".
65        // We claim support for all renderers.
66        true
67    }
68}