microcad_lang_markdown/
mdbook.rs1use std::collections::HashMap;
7
8use thiserror::Error;
9
10use crate::{CodeBlock, Markdown, MarkdownError};
11
12#[derive(Debug, Error)]
13pub enum MdBookError {
14 #[error("IO error: {0}")]
16 IoError(#[from] std::io::Error),
17
18 #[error("No mdbook in directory: {0}")]
20 NoMdBookDirectory(std::path::PathBuf),
21
22 #[error("Error saving markdown file `{file}`: {err}")]
23 Save {
24 file: std::path::PathBuf,
25 err: MarkdownError,
26 },
27}
28
29pub struct MdBook {
31 pub name: String,
32
33 pub md_files: HashMap<std::path::PathBuf, Markdown>,
35
36 pub src_path: std::path::PathBuf,
38}
39
40impl MdBook {
41 pub fn new(path: impl AsRef<std::path::Path>) -> Result<Self, MdBookError> {
46 let root = path.as_ref();
47
48 let root = if root.ends_with("book.toml") {
49 root.parent()
50 .map(|path| path.to_path_buf())
51 .unwrap_or(std::env::current_dir()?)
52 } else {
53 root.to_path_buf()
54 };
55
56 if !root.join("book.toml").exists() {
58 return Err(MdBookError::NoMdBookDirectory(root));
59 }
60
61 let src_path = root.join("src");
63 let mut md_files = Vec::new();
64
65 if src_path.exists() && src_path.is_dir() {
67 Self::visit_dirs(&src_path, &src_path, &mut md_files);
68 }
69
70 let md_files = md_files
71 .iter()
72 .map(|md_file| {
73 (
74 md_file.clone(),
75 Markdown::load(src_path.join(md_file))
76 .unwrap_or_else(|_| panic!("No error: {}", md_file.display())),
77 )
78 })
79 .collect();
80
81 let name = root
82 .file_name()
83 .expect("Some directory name")
84 .to_str()
85 .expect("Valid string")
86 .to_string();
87
88 Ok(Self {
89 name,
90 src_path,
91 md_files,
92 })
93 }
94
95 pub fn abs_md_file(&self, md_file: impl AsRef<std::path::Path>) -> std::path::PathBuf {
96 self.src_path.join(md_file.as_ref())
97 }
98
99 pub fn save_all(&self) -> Result<(), MdBookError> {
100 self.md_files.iter().try_for_each(|(md_file, md)| {
101 md.save(self.abs_md_file(md_file))
102 .map_err(|err| MdBookError::Save {
103 file: md_file.clone(),
104 err,
105 })
106 })
107 }
108
109 pub fn code_blocks(&self) -> impl Iterator<Item = (std::path::PathBuf, &CodeBlock)> {
111 self.md_files.iter().flat_map(|(md_file, md)| {
112 md.code_blocks()
113 .map(|code_block| (md_file.clone(), code_block))
114 })
115 }
116
117 pub fn code_blocks_mut(
119 &mut self,
120 ) -> impl Iterator<Item = (std::path::PathBuf, &mut CodeBlock)> {
121 self.md_files.iter_mut().flat_map(|(md_file, md)| {
122 md.code_blocks_mut()
123 .map(|code_block| (md_file.clone(), code_block))
124 })
125 }
126
127 fn visit_dirs(
131 src_root: &std::path::Path,
132 dir: &std::path::Path,
133 cb: &mut Vec<std::path::PathBuf>,
134 ) {
135 if let Ok(entries) = std::fs::read_dir(dir) {
136 for entry in entries.flatten() {
137 let path = entry.path();
138 if path.is_dir() {
139 Self::visit_dirs(src_root, &path, cb);
140 } else if path.extension().and_then(|s| s.to_str()) == Some("md") {
141 if let Ok(relative) = path.strip_prefix(src_root) {
143 cb.push(relative.to_path_buf());
144 }
145 }
146 }
147 }
148 }
149}