1use std::collections::HashSet;
8use std::fmt::Write;
9
10use rustdoc_types::{Id, Item, ItemEnum};
11
12use crate::generator::context::RenderContext;
13use crate::generator::items::ItemRenderer;
14
15pub struct ModuleRenderer<'a> {
26 ctx: &'a dyn RenderContext,
28
29 current_file: &'a str,
31
32 is_root: bool,
34}
35
36impl<'a> ModuleRenderer<'a> {
37 pub fn new(ctx: &'a dyn RenderContext, current_file: &'a str, is_root: bool) -> Self {
45 Self {
46 ctx,
47 current_file,
48 is_root,
49 }
50 }
51
52 fn process_docs(&self, item: &Item) -> Option<String> {
57 self.ctx.process_docs(item, self.current_file)
58 }
59
60 #[must_use]
80 pub fn render(&self, item: &Item) -> String {
81 let mut md = String::new();
82
83 let name = item.name.as_deref().unwrap_or("crate");
84
85 if self.is_root {
87 _ = write!(md, "# Crate `{name}`\n\n");
88 if let Some(version) = self.ctx.crate_version() {
89 _ = write!(md, "**Version:** {version}\n\n");
90 }
91 } else {
92 _ = write!(md, "# Module `{name}`\n\n");
93 }
94
95 if let Some(docs) = self.process_docs(item) {
97 md.push_str(&docs);
98 md.push_str("\n\n");
99 }
100
101 if let ItemEnum::Module(module) = &item.inner {
103 let categorized = self.categorize_items(&module.items);
104 self.render_all_sections(&mut md, &categorized);
105 }
106
107 md
108 }
109
110 fn categorize_items(&self, item_ids: &'a [Id]) -> CategorizedItems<'a> {
112 let mut items = CategorizedItems::default();
113 let mut seen_items: HashSet<&Id> = HashSet::new();
114
115 for item_id in item_ids {
116 if !seen_items.insert(item_id) {
118 continue;
119 }
120
121 if let Some(child) = self.ctx.get_item(item_id) {
122 if !self.ctx.should_include_item(child) {
123 continue;
124 }
125
126 match &child.inner {
127 ItemEnum::Module(_) => items.modules.push((item_id, child)),
128 ItemEnum::Struct(_) => items.structs.push((item_id, child)),
129 ItemEnum::Enum(_) => items.enums.push((item_id, child)),
130 ItemEnum::Trait(_) => items.traits.push((item_id, child)),
131 ItemEnum::Function(_) => items.functions.push(child),
132 ItemEnum::Macro(_) => items.macros.push(child),
133 ItemEnum::Constant { .. } => items.constants.push(child),
134 ItemEnum::TypeAlias(_) => items.type_aliases.push(child),
135
136 ItemEnum::Use(use_item) => {
138 if use_item.is_glob {
139 self.expand_glob_reexport(&mut items, use_item, &mut seen_items);
141 } else if let Some(target_id) = &use_item.id
142 && let Some(target_item) = self.ctx.get_item(target_id)
143 {
144 match &target_item.inner {
146 ItemEnum::Module(_) => items.modules.push((item_id, child)),
147 ItemEnum::Struct(_) => items.structs.push((item_id, child)),
148 ItemEnum::Enum(_) => items.enums.push((item_id, child)),
149 ItemEnum::Trait(_) => items.traits.push((item_id, child)),
150 ItemEnum::Function(_) => items.functions.push(child),
151 ItemEnum::Macro(_) => items.macros.push(child),
152 ItemEnum::Constant { .. } => items.constants.push(child),
153 ItemEnum::TypeAlias(_) => items.type_aliases.push(child),
154 _ => {},
155 }
156 }
157 },
158 _ => {},
159 }
160 }
161 }
162
163 items.sort();
165
166 items
167 }
168
169 fn expand_glob_reexport(
171 &self,
172 items: &mut CategorizedItems<'a>,
173 use_item: &rustdoc_types::Use,
174 seen_items: &mut HashSet<&'a Id>,
175 ) {
176 let Some(target_id) = &use_item.id else { return };
178
179 let Some(target_module) = self.ctx.get_item(target_id) else { return };
181
182 let ItemEnum::Module(module) = &target_module.inner else { return };
184
185 for child_id in &module.items {
187 if !seen_items.insert(child_id) {
189 continue;
190 }
191
192 let Some(child) = self.ctx.get_item(child_id) else { continue };
193
194 if !self.ctx.should_include_item(child) {
196 continue;
197 }
198
199 match &child.inner {
201 ItemEnum::Module(_) => items.modules.push((child_id, child)),
202 ItemEnum::Struct(_) => items.structs.push((child_id, child)),
203 ItemEnum::Enum(_) => items.enums.push((child_id, child)),
204 ItemEnum::Trait(_) => items.traits.push((child_id, child)),
205 ItemEnum::Function(_) => items.functions.push(child),
206 ItemEnum::Macro(_) => items.macros.push(child),
207 ItemEnum::Constant { .. } => items.constants.push(child),
208 ItemEnum::TypeAlias(_) => items.type_aliases.push(child),
209 _ => {},
210 }
211 }
212 }
213
214 fn render_all_sections(&self, md: &mut String, items: &CategorizedItems) {
216 self.render_modules_section(md, &items.modules);
217 self.render_structs_section(md, &items.structs);
218 self.render_enums_section(md, &items.enums);
219 self.render_traits_section(md, &items.traits);
220 self.render_functions_section(md, &items.functions);
221 self.render_macros_section(md, &items.macros);
222 self.render_constants_section(md, &items.constants);
223 self.render_type_aliases_section(md, &items.type_aliases);
224 }
225
226 fn render_modules_section(&self, md: &mut String, modules: &[(&Id, &Item)]) {
228 if modules.is_empty() {
229 return;
230 }
231
232 md.push_str("## Modules\n\n");
233 for (module_id, module_item) in modules {
234 let module_name = module_item.name.as_deref().unwrap_or("unnamed");
235
236 if let Some(link) = self.ctx.create_link(**module_id, self.current_file) {
237 _ = write!(md, "- {link}");
238 } else {
239 _ = write!(md, "- **`{module_name}`**");
240 }
241
242 if let Some(docs) = &module_item.docs
243 && let Some(first_line) = docs.lines().next()
244 {
245 _ = write!(md, " - {first_line}");
246 }
247 md.push('\n');
248 }
249 md.push('\n');
250 }
251
252 fn render_structs_section(&self, md: &mut String, structs: &[(&Id, &Item)]) {
254 if structs.is_empty() {
255 return;
256 }
257
258 md.push_str("## Structs\n\n");
259 let renderer = ItemRenderer::new(self.ctx, self.current_file);
260 for (item_id, struct_item) in structs {
261 renderer.render_struct(md, **item_id, struct_item);
262 }
263 }
264
265 fn render_enums_section(&self, md: &mut String, enums: &[(&Id, &Item)]) {
267 if enums.is_empty() {
268 return;
269 }
270
271 md.push_str("## Enums\n\n");
272 let renderer = ItemRenderer::new(self.ctx, self.current_file);
273 for (item_id, enum_item) in enums {
274 renderer.render_enum(md, **item_id, enum_item);
275 }
276 }
277
278 fn render_traits_section(&self, md: &mut String, traits: &[(&Id, &Item)]) {
280 if traits.is_empty() {
281 return;
282 }
283
284 md.push_str("## Traits\n\n");
285 let renderer = ItemRenderer::new(self.ctx, self.current_file);
286 for (_item_id, trait_item) in traits {
287 renderer.render_trait(md, trait_item);
288 }
289 }
290
291 fn render_functions_section(&self, md: &mut String, functions: &[&Item]) {
293 if functions.is_empty() {
294 return;
295 }
296
297 md.push_str("## Functions\n\n");
298 let renderer = ItemRenderer::new(self.ctx, self.current_file);
299 for func_item in functions {
300 renderer.render_function(md, func_item);
301 }
302 }
303
304 fn render_macros_section(&self, md: &mut String, macros: &[&Item]) {
306 if macros.is_empty() {
307 return;
308 }
309
310 md.push_str("## Macros\n\n");
311 let renderer = ItemRenderer::new(self.ctx, self.current_file);
312 for macro_item in macros {
313 renderer.render_macro(md, macro_item);
314 }
315 }
316
317 fn render_constants_section(&self, md: &mut String, constants: &[&Item]) {
319 if constants.is_empty() {
320 return;
321 }
322
323 md.push_str("## Constants\n\n");
324 let renderer = ItemRenderer::new(self.ctx, self.current_file);
325 for const_item in constants {
326 renderer.render_constant(md, const_item);
327 }
328 }
329
330 fn render_type_aliases_section(&self, md: &mut String, type_aliases: &[&Item]) {
332 if type_aliases.is_empty() {
333 return;
334 }
335
336 md.push_str("## Type Aliases\n\n");
337 let renderer = ItemRenderer::new(self.ctx, self.current_file);
338 for alias_item in type_aliases {
339 renderer.render_type_alias(md, alias_item);
340 }
341 }
342}
343
344#[derive(Default)]
349struct CategorizedItems<'a> {
350 modules: Vec<(&'a Id, &'a Item)>,
352
353 structs: Vec<(&'a Id, &'a Item)>,
355
356 enums: Vec<(&'a Id, &'a Item)>,
358
359 traits: Vec<(&'a Id, &'a Item)>,
361
362 functions: Vec<&'a Item>,
364
365 macros: Vec<&'a Item>,
367
368 constants: Vec<&'a Item>,
370
371 type_aliases: Vec<&'a Item>,
373}
374
375impl<'a> CategorizedItems<'a> {
376 fn sort(&mut self) {
381 fn item_name(item: &Item) -> &str {
383 item.name.as_deref().unwrap_or("")
384 }
385
386 self.modules.sort_by(|a, b| item_name(a.1).cmp(item_name(b.1)));
388 self.structs.sort_by(|a, b| item_name(a.1).cmp(item_name(b.1)));
389 self.enums.sort_by(|a, b| item_name(a.1).cmp(item_name(b.1)));
390 self.traits.sort_by(|a, b| item_name(a.1).cmp(item_name(b.1)));
391
392 self.functions.sort_by(|a, b| item_name(a).cmp(item_name(b)));
394 self.macros.sort_by(|a, b| item_name(a).cmp(item_name(b)));
395 self.constants.sort_by(|a, b| item_name(a).cmp(item_name(b)));
396 self.type_aliases.sort_by(|a, b| item_name(a).cmp(item_name(b)));
397 }
398}