1use lazy_static::lazy_static;
2use mdbook::book::{Book, BookItem, Chapter};
3use mdbook::errors::{Error, Result};
4use mdbook::preprocess::{Preprocessor, PreprocessorContext};
5use regex::Regex;
6use std::path::{Path,PathBuf};
7
8pub mod script;
9
10lazy_static! {
11 static ref RE: Regex = Regex::new("^\\{\\{#tutorial (.*)\\}\\}$").unwrap();
12}
13
14pub struct BashTutorial;
16
17impl BashTutorial {
18 pub fn new() -> BashTutorial {
19 BashTutorial
20 }
21}
22
23impl Preprocessor for BashTutorial {
24 fn name(&self) -> &str {
25 "bash-tutorial"
26 }
27
28 fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
29 env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
30 let src_dir = ctx.root.join(&ctx.config.book.src);
31
32 book.for_each_mut(|section: &mut BookItem| {
33 if let BookItem::Chapter(ref mut chapter) = section {
34 if let Some(ref _source) = chapter.path {
35 let content = BashTutorial::add_tutorial(&src_dir, chapter)
36 .expect("error adding tutorial in chapter {chapter}");
37 chapter.content = content;
38 }
39 }
40 });
41
42 Ok(book)
43 }
44
45 fn supports_renderer(&self, renderer: &str) -> bool {
46 renderer == "html"
47 }
48}
49
50impl BashTutorial {
51 fn add_tutorial(root: &PathBuf, chapter: &mut Chapter) -> Result<String> {
52 add_tutorial(root, &chapter.content)
53 }
54}
55
56fn add_tutorial(root: &PathBuf, content: &str) -> Result<String> {
57 let v = content
58 .lines()
59 .map(|l| {
60 if !RE.is_match(l) {
61 return l.to_string();
62 }
63
64 let f = l
65 .trim_start_matches("{{#tutorial ")
66 .trim_end_matches("}}");
67
68 let sf = Path::new(f);
69 let v = root.join(&sf);
70 if v.exists() {
71 let fp = v.as_path().to_str().expect("error joining file");
72 return script::parse(fp).to_string();
73 }
74 return l.to_string();
75 })
76 .collect::<Vec<String>>()
77 .join("\n");
78 Ok(v)
79}
80
81#[cfg(test)]
82mod test {
83 use super::*;
84
85 #[test]
86 fn nop_preprocessor_run() {
87 let input_json = r##"[
88 {
89 "root": ".",
90 "config": {
91 "book": {
92 "authors": ["AUTHOR"],
93 "language": "en",
94 "multilingual": false,
95 "src": ".",
96 "title": "TITLE"
97 },
98 "preprocessor": {
99 "nop": {}
100 }
101 },
102 "renderer": "html",
103 "mdbook_version": "0.4.21"
104 },
105 {
106 "sections": [
107 {
108 "Chapter": {
109 "name": "Chapter 1",
110 "content": "{{#tutorial data/test.sh}}\n",
111 "number": [1],
112 "sub_items": [],
113 "path": "chapter_1.md",
114 "source_path": "chapter_1.md",
115 "parent_names": []
116 }
117 }
118 ],
119 "__non_exhaustive": null
120 }
121 ]"##;
122 let input_json = input_json.as_bytes();
123
124 let (ctx, book) = mdbook::preprocess::CmdPreprocessor::parse_input(input_json).unwrap();
125 let output_json = r##"[
126 {
127 "root": ".",
128 "config": {
129 "book": {
130 "authors": ["AUTHOR"],
131 "language": "en",
132 "multilingual": false,
133 "src": "src",
134 "title": "TITLE"
135 },
136 "preprocessor": {
137 "nop": {}
138 }
139 },
140 "renderer": "html",
141 "mdbook_version": "0.4.21"
142 },
143 {
144 "sections": [
145 {
146 "Chapter": {
147 "name": "Chapter 1",
148 "content": "1. Test\n\n\t```bash\n\techo \"test\"\n\t```\n",
149 "number": [1],
150 "sub_items": [],
151 "path": "chapter_1.md",
152 "source_path": "chapter_1.md",
153 "parent_names": []
154 }
155 }
156 ],
157 "__non_exhaustive": null
158 }
159 ]"##;
160 let output_json = output_json.as_bytes();
161
162 let (_ctx, expected_book) = mdbook::preprocess::CmdPreprocessor::parse_input(output_json).unwrap();
163 let result = BashTutorial::new().run(&ctx, book);
164 assert!(result.is_ok());
165
166 let actual_book = result.unwrap();
168 assert_eq!(actual_book, expected_book);
169 }
170}