Skip to main content

mdbook_scientific/
lib.rs

1pub mod error;
2mod fragments;
3mod preprocess;
4
5use crate::error::Error;
6use fs_err as fs;
7use std::collections::HashMap;
8use std::path::Path;
9use std::path::PathBuf;
10
11use mdbook::book::{Book, BookItem, Chapter};
12use mdbook::preprocess::{Preprocessor, PreprocessorContext};
13use nom_bibtex::*;
14
15use preprocess::{replace_blocks, replace_inline_blocks};
16
17pub struct Scientific;
18
19impl Scientific {
20    pub fn new() -> Scientific {
21        Scientific
22    }
23}
24
25impl Preprocessor for Scientific {
26    fn name(&self) -> &str {
27        "scientific"
28    }
29
30    fn supports_renderer(&self, renderer: &str) -> bool {
31        renderer != "not-supported"
32    }
33
34    fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book, mdbook::errors::Error> {
35        self.run_inner(ctx, book)
36            .map_err(mdbook::errors::Error::new)
37    }
38}
39
40impl Scientific {
41    fn run_inner(&self, ctx: &PreprocessorContext, mut book: Book) -> crate::error::Result<Book> {
42        if let Some(cfg) = ctx.config.get_preprocessor(self.name()) {
43            let fragment_path = cfg
44                .get("fragment_path")
45                .map(|x| x.as_str().expect("Fragment path is valid UTF8. qed"))
46                .unwrap_or("fragments/");
47
48            let fragment_path = Path::new(fragment_path);
49
50            fs::create_dir_all(fragment_path)?;
51
52            let fragment_path = fs::canonicalize(fragment_path)?;
53
54            // track which fragments we use to copy them into the assets folder
55            let mut used_fragments = Vec::new();
56            // track which references are created
57            let mut references = HashMap::new();
58            // if there occurs an error skip everything and return the error
59            let mut error = Ok::<_, Error>(());
60
61            // load all references in the bibliography and export to html
62            if let (Some(bib), Some(bib2xhtml)) = (cfg.get("bibliography"), cfg.get("bib2xhtml")) {
63                let bib = bib.as_str().unwrap();
64                let bib2xhtml = bib2xhtml.as_str().expect("bib string is valid UTF8. qed");
65
66                if !Path::new(bib).exists() {
67                    return Err(Error::BibliographyMissing(bib.to_owned()));
68                }
69
70                // read entries in bibtex file
71                let bibtex = fs::read_to_string(bib)?;
72                let bibtex = Bibtex::parse(&bibtex)?;
73                for (i, entry) in bibtex.bibliographies().into_iter().enumerate() {
74                    references.insert(entry.citation_key().to_string(), format!("[{}]", i + 1));
75                }
76
77                // create bibliography
78                let content = fragments::bib_to_html(&bib, &bib2xhtml)?;
79
80                // add final chapter for bibliography
81                let bib_chapter = Chapter::new(
82                    "Bibliography",
83                    format!("# Bibliography\n{}", content),
84                    PathBuf::from("bibliography.md"),
85                    Vec::new(),
86                );
87                book.push_item(bib_chapter);
88            }
89
90            // assets path
91            let asset_path = cfg
92                .get("assets")
93                .map(|x| x.as_str().expect("Assumes valid UTF8 for assets. qed"))
94                .unwrap_or("src/");
95            let asset_path = ctx.root.join(asset_path);
96
97            // process blocks like `$$ .. $$`
98            book.for_each_mut(|item| {
99                if let Err(_) = error {
100                    return;
101                }
102
103                if let BookItem::Chapter(ref mut ch) = item {
104                    let head_number = ch
105                        .number
106                        .as_ref()
107                        .map(|x| x.to_string())
108                        .unwrap_or(String::new());
109
110                    match replace_blocks(
111                        &fragment_path,
112                        &asset_path,
113                        &ch.content,
114                        &head_number,
115                        &mut used_fragments,
116                        &mut references,
117                    ) {
118                        Ok(x) => ch.content = x,
119                        Err(err) => error = Err(Error::from(err)),
120                    }
121                }
122            });
123
124            // process inline blocks like `$ .. $`
125            book.for_each_mut(|item| {
126                if error.is_err() {
127                    return;
128                }
129
130                if let BookItem::Chapter(ref mut ch) = item {
131                    let _head_number = ch
132                        .number
133                        .as_ref()
134                        .map(|x| format!("{}", x))
135                        .unwrap_or("".into());
136
137                    match replace_inline_blocks(
138                        &fragment_path,
139                        &ch.content,
140                        &references,
141                        &mut used_fragments,
142                    ) {
143                        Ok(x) => ch.content = x,
144                        Err(err) => error = Err(Error::from(err)),
145                    }
146                }
147            });
148
149            error?;
150
151            // the output path is `src/assets`, which get copied to the output directory
152            let dest = ctx.root.join("src").join("assets");
153            if !dest.exists() {
154                fs::create_dir_all(&dest)?;
155            }
156
157            // copy all fragments
158            for fragment in used_fragments {
159                fs::copy(fragment_path.join(&fragment), dest.join(&fragment))?;
160            }
161
162            Ok(book)
163        } else {
164            Err(Error::KeySectionNotFound)
165        }
166    }
167}