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 let mut used_fragments = Vec::new();
56 let mut references = HashMap::new();
58 let mut error = Ok::<_, Error>(());
60
61 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 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 let content = fragments::bib_to_html(&bib, &bib2xhtml)?;
79
80 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 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 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 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 let dest = ctx.root.join("src").join("assets");
153 if !dest.exists() {
154 fs::create_dir_all(&dest)?;
155 }
156
157 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}