microcad_docgen/mdbook/
mod.rs1use std::io::Write;
7
8use microcad_builtin::Symbol;
9use microcad_lang::{builtin::Builtin, symbol::SymbolDef};
10
11use crate::{DocGen, md::ToMd};
12
13pub struct MdBook {
17 pub path: std::path::PathBuf,
19}
20
21impl MdBook {
22 pub fn new(path: impl AsRef<std::path::Path>) -> Self {
23 Self {
24 path: path.as_ref().to_path_buf(),
25 }
26 }
27
28 fn generate_book_toml_string(&self) -> String {
30 let book_toml: toml::Value =
31 toml::de::from_str(include_str!("book.toml")).expect("Valid toml");
32 let str = toml::ser::to_string(&book_toml).expect("No error");
33 format!(
34 r#"# Copyright © 2026 The µcad authors <info@ucad.xyz>
35# SPDX-License-Identifier: AGPL-3.0-or-later
36#
37# NOTE: Auto-generated code.
38# This markdown book has been generated from µcad source via `microcad-docgen`.
39# Changes in the book might be overwritten.
40{str}
41"#
42 )
43 }
44
45 fn write_book_toml(&self) -> std::io::Result<()> {
47 let mut file = std::fs::File::create(self.path.join("book.toml"))?;
48 file.write_all(self.generate_book_toml_string().as_bytes())?;
49 Ok(())
50 }
51
52 fn symbol_path(symbol: &Symbol) -> std::path::PathBuf {
56 let path: std::path::PathBuf = symbol
57 .full_name()
58 .iter()
59 .skip(1)
60 .map(|id| id.to_string())
61 .collect();
62 symbol.with_def(|def| match def {
63 SymbolDef::SourceFile(..) | SymbolDef::Module(..) => path.join("README.md"),
64 _ => {
65 let mut path = path.clone();
66 path.set_extension("md");
67 path
68 }
69 })
70 }
71
72 fn _generate_summary(
73 &self,
74 writer: &mut impl std::fmt::Write,
75 symbol: &Symbol,
76 depth: usize,
77 ) -> std::fmt::Result {
78 fn entry(
79 writer: &mut impl std::fmt::Write,
80 id: impl std::fmt::Display,
81 path: impl AsRef<std::path::Path>,
82 depth: usize,
83 ) -> std::fmt::Result {
84 writeln!(
85 writer,
86 "{:indent$}- [`{id}`]({path})",
87 "",
88 indent = 2 * depth,
89 path = path.as_ref().display()
90 )
91 }
92
93 fn recurse<'a>(
94 self_: &MdBook,
95 writer: &mut impl std::fmt::Write,
96 symbols: impl IntoIterator<Item = &'a Symbol>,
97 depth: usize,
98 ) -> std::fmt::Result {
99 symbols
100 .into_iter()
101 .try_for_each(|symbol| self_._generate_summary(writer, symbol, depth))
102 }
103
104 let path = Self::symbol_path(symbol);
105
106 entry(writer, symbol.id(), path, depth)?;
107 let depth = depth + 1;
108
109 let children: Vec<_> = symbol.iter().filter(|symbol| symbol.is_public()).collect();
110
111 let modules: Vec<_> = children
112 .iter()
113 .filter(|symbol| {
114 symbol.with_def(|def| {
115 matches!(def, SymbolDef::SourceFile(..) | SymbolDef::Module(..))
116 })
117 })
118 .collect();
119
120 if !modules.is_empty() {
121 recurse(self, writer, modules.into_iter(), depth)?;
122 }
123
124 let workbenches: Vec<_> = children
126 .iter()
127 .filter(|symbol| {
128 symbol.with_def(|def| {
129 matches!(
130 def,
131 SymbolDef::Workbench(_) | SymbolDef::Builtin(Builtin::Workbench(_))
132 )
133 })
134 })
135 .collect();
136
137 if !workbenches.is_empty() {
138 recurse(self, writer, workbenches.into_iter(), depth)?;
139 }
140
141 Ok(())
142 }
143
144 fn generate_summary(
145 &self,
146 writer: &mut impl std::fmt::Write,
147 symbol: &Symbol,
148 ) -> std::fmt::Result {
149 writeln!(writer, "# Summary")?;
150 writeln!(writer)?;
151 self._generate_summary(writer, symbol, 0)
152 }
153
154 fn write_symbol(&self, symbol: &Symbol) -> std::io::Result<()> {
155 symbol.riter().try_for_each(|symbol| {
156 let path = &self.path.join("src").join(Self::symbol_path(&symbol));
157 std::fs::create_dir_all(path.parent().expect("A parent"))?;
158 symbol.with_def(|def| match def {
159 SymbolDef::SourceFile(_)
160 | SymbolDef::Module(_)
161 | SymbolDef::Workbench(_)
162 | SymbolDef::Builtin(Builtin::Workbench(_)) => symbol.to_md().write(path),
163 _ => Ok(()),
164 })
165 })
166 }
167
168 fn write_summary(&self, symbol: &Symbol) -> std::io::Result<()> {
169 let mut file = std::fs::File::create(self.path.join("src").join("SUMMARY.md"))?;
171
172 let mut buffer = String::new();
174
175 self.generate_summary(&mut buffer, symbol)
178 .map_err(std::io::Error::other)?;
179 file.write_all(buffer.as_bytes())?;
180 Ok(())
181 }
182}
183
184impl DocGen for MdBook {
185 fn doc_gen(&self, symbol: &Symbol) -> std::io::Result<()> {
186 std::fs::create_dir_all(self.path.join("src"))?;
187
188 self.write_book_toml()?;
189 self.write_summary(symbol)?;
190 self.write_symbol(symbol)
191 }
192}