1use gruel_parser::ast::{Ast, Doc, EnumDecl, Item, LinkExternBlock, StructDecl};
8use lasso::ThreadedRodeo;
9
10pub mod markdown;
11pub use markdown::render_markdown;
12
13pub mod html;
14pub use html::render_html;
15
16pub mod links;
17pub use links::LinkTable;
18
19#[derive(Debug, Clone)]
21pub struct DocFile {
22 pub stem: String,
25 pub module_doc: Option<Doc>,
27 pub items: Vec<DocItem>,
29}
30
31#[derive(Debug, Clone)]
33pub struct DocItem {
34 pub slug: String,
36 pub name: String,
38 pub kind: ItemKind,
40 pub doc: Option<Doc>,
42 pub detail: ItemDetail,
44}
45
46#[derive(Debug, Clone, Copy)]
48pub enum ItemKind {
49 Function,
50 Struct,
51 Enum,
52 Interface,
53 Derive,
54 Const,
55 LinkExtern,
56}
57
58impl ItemKind {
59 pub fn label(self) -> &'static str {
60 match self {
61 ItemKind::Function => "fn",
62 ItemKind::Struct => "struct",
63 ItemKind::Enum => "enum",
64 ItemKind::Interface => "interface",
65 ItemKind::Derive => "derive",
66 ItemKind::Const => "const",
67 ItemKind::LinkExtern => "link_extern",
68 }
69 }
70}
71
72#[derive(Debug, Clone, Default)]
76pub struct ItemDetail {
77 pub fields: Vec<NamedDoc>,
79 pub variants: Vec<NamedDoc>,
81 pub methods: Vec<NamedDoc>,
83 pub extern_fns: Vec<NamedDoc>,
85}
86
87#[derive(Debug, Clone)]
89pub struct NamedDoc {
90 pub name: String,
91 pub doc: Option<Doc>,
92}
93
94#[derive(Debug, Clone, Default)]
96pub struct DocSite {
97 pub files: Vec<DocFile>,
98}
99
100impl DocSite {
101 pub fn from_ast(stem: impl Into<String>, ast: &Ast, interner: &ThreadedRodeo) -> DocFile {
107 let mut items = Vec::new();
108 for item in &ast.items {
109 if let Some(doc_item) = item_to_doc_item(item, interner) {
110 items.push(doc_item);
111 }
112 }
113 DocFile {
114 stem: stem.into(),
115 module_doc: ast.module_doc.clone(),
116 items,
117 }
118 }
119
120 pub fn push(&mut self, file: DocFile) {
122 self.files.push(file);
123 }
124
125 pub fn link_table(&self) -> LinkTable {
130 let mut table = LinkTable::new();
131 for file in &self.files {
132 for item in &file.items {
133 let slug_with_dir = format!("{}/{}", file.stem, item.slug);
134 table.insert(&item.name, item.kind.label(), &slug_with_dir);
135 }
136 }
137 table
138 }
139}
140
141impl DocFile {
142 pub fn link_table(&self) -> LinkTable {
146 let mut table = LinkTable::new();
147 for item in &self.items {
148 table.insert(&item.name, item.kind.label(), &item.slug);
149 }
150 table
151 }
152}
153
154fn item_to_doc_item(item: &Item, interner: &ThreadedRodeo) -> Option<DocItem> {
155 match item {
156 Item::Function(f) => Some(DocItem {
157 slug: format!("fn.{}", interner.resolve(&f.name.name)),
158 name: interner.resolve(&f.name.name).to_string(),
159 kind: ItemKind::Function,
160 doc: f.doc.clone(),
161 detail: ItemDetail::default(),
162 }),
163 Item::Struct(s) => Some(struct_doc_item(s, interner)),
164 Item::Enum(e) => Some(enum_doc_item(e, interner)),
165 Item::Interface(i) => Some(DocItem {
166 slug: format!("interface.{}", interner.resolve(&i.name.name)),
167 name: interner.resolve(&i.name.name).to_string(),
168 kind: ItemKind::Interface,
169 doc: i.doc.clone(),
170 detail: ItemDetail {
171 methods: i
172 .methods
173 .iter()
174 .map(|m| NamedDoc {
175 name: interner.resolve(&m.name.name).to_string(),
176 doc: m.doc.clone(),
177 })
178 .collect(),
179 ..ItemDetail::default()
180 },
181 }),
182 Item::Derive(d) => Some(DocItem {
183 slug: format!("derive.{}", interner.resolve(&d.name.name)),
184 name: interner.resolve(&d.name.name).to_string(),
185 kind: ItemKind::Derive,
186 doc: d.doc.clone(),
187 detail: ItemDetail {
188 methods: d
189 .methods
190 .iter()
191 .map(|m| NamedDoc {
192 name: interner.resolve(&m.name.name).to_string(),
193 doc: m.doc.clone(),
194 })
195 .collect(),
196 ..ItemDetail::default()
197 },
198 }),
199 Item::Const(c) => Some(DocItem {
200 slug: format!("const.{}", interner.resolve(&c.name.name)),
201 name: interner.resolve(&c.name.name).to_string(),
202 kind: ItemKind::Const,
203 doc: c.doc.clone(),
204 detail: ItemDetail::default(),
205 }),
206 Item::LinkExtern(b) => Some(link_extern_doc_item(b, interner)),
207 Item::Error(_) => None,
208 }
209}
210
211fn struct_doc_item(s: &StructDecl, interner: &ThreadedRodeo) -> DocItem {
212 DocItem {
213 slug: format!("struct.{}", interner.resolve(&s.name.name)),
214 name: interner.resolve(&s.name.name).to_string(),
215 kind: ItemKind::Struct,
216 doc: s.doc.clone(),
217 detail: ItemDetail {
218 fields: s
219 .fields
220 .iter()
221 .map(|f| NamedDoc {
222 name: interner.resolve(&f.name.name).to_string(),
223 doc: f.doc.clone(),
224 })
225 .collect(),
226 methods: s
227 .methods
228 .iter()
229 .map(|m| NamedDoc {
230 name: interner.resolve(&m.name.name).to_string(),
231 doc: m.doc.clone(),
232 })
233 .collect(),
234 ..ItemDetail::default()
235 },
236 }
237}
238
239fn enum_doc_item(e: &EnumDecl, interner: &ThreadedRodeo) -> DocItem {
240 DocItem {
241 slug: format!("enum.{}", interner.resolve(&e.name.name)),
242 name: interner.resolve(&e.name.name).to_string(),
243 kind: ItemKind::Enum,
244 doc: e.doc.clone(),
245 detail: ItemDetail {
246 variants: e
247 .variants
248 .iter()
249 .map(|v| NamedDoc {
250 name: interner.resolve(&v.name.name).to_string(),
251 doc: v.doc.clone(),
252 })
253 .collect(),
254 methods: e
255 .methods
256 .iter()
257 .map(|m| NamedDoc {
258 name: interner.resolve(&m.name.name).to_string(),
259 doc: m.doc.clone(),
260 })
261 .collect(),
262 ..ItemDetail::default()
263 },
264 }
265}
266
267fn link_extern_doc_item(b: &LinkExternBlock, interner: &ThreadedRodeo) -> DocItem {
268 let name = interner.resolve(&b.library.value).to_string();
269 DocItem {
270 slug: format!("link_extern.{}", &name),
271 name,
272 kind: ItemKind::LinkExtern,
273 doc: b.doc.clone(),
274 detail: ItemDetail {
275 extern_fns: b
276 .items
277 .iter()
278 .map(|f| NamedDoc {
279 name: interner.resolve(&f.name.name).to_string(),
280 doc: f.doc.clone(),
281 })
282 .collect(),
283 ..ItemDetail::default()
284 },
285 }
286}