sphinx_rustdocgen/directives/
module_directive.rs1use std::cmp::max;
20use std::collections::BTreeMap;
21use std::path::{Path, PathBuf};
22
23use syn::{ItemMod, Meta};
24
25use crate::directives::directive_options::{DirectiveOption, DirectiveVisibility, IndexEntryType};
26use crate::directives::{
27 extract_doc_from_attrs,
28 order_items,
29 Directive,
30 FileDirectives,
31 ImplDirective,
32 UseDirective,
33};
34use crate::formats::{Format, MdContent, MdDirective, RstContent, RstDirective};
35use crate::utils::SourceCodeFile;
36
37#[derive(Clone, Debug)]
39pub struct ModuleDirective {
40 pub(crate) name: String,
42 pub(crate) options: Vec<DirectiveOption>,
44 pub(crate) content: Vec<String>,
46 pub(crate) ident: String,
48 pub(crate) source_code_file: SourceCodeFile,
50 pub(crate) file_directives: FileDirectives,
52}
53
54#[inline]
56fn has_test_token(tokens: &str) -> bool {
57 tokens.split(',').any(|t| t.trim() == "test")
58}
59
60fn find_file_under_dir(module_ident: &str, directory: &Path) -> Option<PathBuf> {
71 let mut mod_file = directory.join(format!("{module_ident}.rs"));
73 if mod_file.is_file() {
74 return Some(mod_file);
75 }
76
77 mod_file = directory.join(module_ident).join("mod.rs");
79 if mod_file.is_file() {
80 return Some(mod_file);
81 }
82
83 None
84}
85
86fn get_module_file(module_ident: &str, parent_file: &SourceCodeFile) -> PathBuf {
93 if parent_file.path.is_dir() {
94 find_file_under_dir(module_ident, &parent_file.path)
96 .unwrap_or(parent_file.path.join(module_ident).join("mod.rs"))
97 }
98 else if parent_file.path.ends_with("mod.rs") {
99 let parent_dir = parent_file.path.parent().unwrap();
101 find_file_under_dir(module_ident, parent_dir)
102 .unwrap_or(parent_dir.join(module_ident).join("mod.rs"))
103 }
104 else {
105 let parent_dir = parent_file
108 .path
109 .parent()
110 .unwrap()
111 .join(parent_file.path.file_stem().unwrap());
112 find_file_under_dir(module_ident, &parent_dir)
113 .unwrap_or(parent_dir.join(module_ident).join("mod.rs"))
114 }
115}
116
117fn is_test_module(item_mod: &ItemMod) -> bool {
119 for attr in &item_mod.attrs {
121 if let Meta::List(meta) = &attr.meta {
122 if meta.path.segments.len() == 1
123 && meta.path.segments.first().unwrap().ident == "cfg"
124 && has_test_token(&meta.tokens.to_string())
125 {
126 return true;
127 }
128 }
129 }
130 false
131}
132
133impl ModuleDirective {
134 const DIRECTIVE_NAME: &'static str = "module";
135
136 pub(crate) fn from_item(parent_file: &SourceCodeFile, item: &ItemMod) -> Option<Self> {
152 if is_test_module(item) {
153 return None;
154 }
155
156 let source_code_file = SourceCodeFile {
159 path: get_module_file(&item.ident.to_string(), parent_file),
160 item: format!("{}::{}", &parent_file.item, item.ident),
161 };
162
163 let mod_items = item.content.as_ref().map(|(_, items)| items);
165 let file_directives = FileDirectives::from_ast_items(
166 mod_items.unwrap_or(&source_code_file.ast().items),
167 &source_code_file,
168 );
169 let mut mod_attrs = item.attrs.clone();
170 mod_attrs.extend(source_code_file.ast().attrs);
171
172 Some(ModuleDirective {
173 name: source_code_file.item.clone(),
174 options: vec![
175 DirectiveOption::Index(IndexEntryType::Normal),
176 DirectiveOption::Vis(DirectiveVisibility::from(&item.vis)),
177 ],
178 content: extract_doc_from_attrs(&mod_attrs),
179 ident: item.ident.to_string(),
180 source_code_file,
181 file_directives,
182 })
183 }
184
185 pub(crate) fn text(self, format: &Format, max_visibility: &DirectiveVisibility) -> Vec<String> {
187 let mut text = format.make_title(&format.make_inline_code(format!("mod {}", self.ident)));
188 text.extend(format.format_directive(self, max_visibility));
189 text
190 }
191
192 pub(crate) fn filter_items(&mut self, max_visibility: &DirectiveVisibility) -> Vec<Directive> {
203 let mut excluded_items = vec![];
204 self.file_directives.modules.retain_mut(|module| {
205 excluded_items.extend(module.filter_items(max_visibility));
207 module.directive_visibility() <= max_visibility
209 });
210
211 let directive_visibility = *self.directive_visibility();
213
214 let mut reexports = vec![];
216 for use_ in &mut self.file_directives.uses {
217 if use_.reexport.is_some() && use_.directive_visibility() <= max_visibility {
218 reexports.push(use_)
219 }
220 }
221
222 if &directive_visibility > max_visibility {
223 while let Some(item) = self.file_directives.items.pop() {
226 if item.directive_visibility() <= max_visibility {
227 excluded_items.push(item);
228 }
229 }
230 'item_loop: for item in excluded_items.iter_mut() {
236 for reexport in &reexports {
237 if reexport.contains(item.name()) {
238 item.change_parent(&self.name);
239 continue 'item_loop;
240 }
241 }
242 }
243 return excluded_items;
244 }
245
246 let mut not_documented = vec![];
249 let mut inlined = BTreeMap::new();
250 'item_loop: for mut item in excluded_items {
251 for reexport in &mut reexports {
252 if reexport.contains(item.name()) {
253 if !matches!(item, Directive::Impl(_)) {
254 let (k, v) = reexport.inline(item.name()).unwrap();
255 inlined.insert(k, v);
256 }
257 item.change_parent(&self.name);
258 item.add_content(reexport.content.clone());
259 self.file_directives.items.push(item);
260 continue 'item_loop;
261 }
262 }
263 not_documented.push(item);
264 }
265 self.file_directives
266 .uses
267 .push(UseDirective::for_use_paths(inlined));
268
269 not_documented
270 }
271
272 pub(crate) fn directive_visibility(&self) -> &DirectiveVisibility {
274 if let DirectiveOption::Vis(v) = &self.options[1] {
275 return v;
276 }
277 unreachable!("Module: order of options changed")
278 }
279
280 pub(crate) fn change_parent(&mut self, new_parent: &str) {
282 self.name = format!("{new_parent}::{}", self.ident);
283 for item in &mut self.file_directives.items {
284 item.change_parent(&self.name);
285 }
286 }
287
288 pub(crate) fn collect_impls(&mut self) -> Vec<ImplDirective> {
290 let mut impls = vec![];
291 impls.append(&mut self.file_directives.impls);
292
293 for module in &mut self.file_directives.modules {
294 impls.extend(module.collect_impls())
295 }
296
297 impls
298 }
299
300 pub(crate) fn claim_impls(&mut self, impls: Vec<ImplDirective>) -> Vec<ImplDirective> {
309 self.file_directives.claim_impls(&self.name, impls)
310 }
311}
312
313impl RstDirective for ModuleDirective {
314 fn get_rst_text(self, level: usize, max_visibility: &DirectiveVisibility) -> Vec<String> {
316 let content_indent = Self::make_content_indent(level);
318
319 let mut text =
321 Self::make_rst_header(Self::DIRECTIVE_NAME, &self.name, &self.options, level);
322 text.extend(self.content.get_rst_text(&content_indent));
323
324 text.extend(Self::make_rst_toctree(
325 &content_indent,
326 "Modules",
327 Some(1),
328 self.file_directives
329 .modules
330 .iter()
331 .map(|m| format!("{}/{}", &self.ident, m.ident)),
332 ));
333
334 let mut reexports = vec![];
335 for use_ in self.file_directives.uses {
336 if use_.reexport.is_some() && use_.directive_visibility() <= max_visibility {
337 for path in use_.paths.values() {
338 reexports.push((path.clone(), use_.content.clone()));
339 }
340 }
341 text.extend(use_.get_rst_text(level + 1, max_visibility));
342 }
343 text.extend(Self::make_rst_list(
344 &content_indent,
345 "Re-exports",
346 &reexports,
347 ));
348
349 for (name, item) in order_items(self.file_directives.items) {
350 text.extend(Self::make_rst_section(name, level, item, max_visibility));
351 }
352
353 text
354 }
355}
356
357impl MdDirective for ModuleDirective {
358 fn get_md_text(self, fence_size: usize, max_visibility: &DirectiveVisibility) -> Vec<String> {
360 let fence = Self::make_fence(max(fence_size, 4));
362
363 let mut text =
365 Self::make_md_header(Self::DIRECTIVE_NAME, &self.name, &self.options, &fence);
366 text.extend(self.content.get_md_text());
367
368 text.extend(Self::make_md_toctree(
369 3,
370 "Modules",
371 Some(1),
372 self.file_directives
373 .modules
374 .iter()
375 .map(|m| format!("{}/{}", &self.ident, m.ident)),
376 ));
377
378 let mut reexports = vec![];
379 for use_ in self.file_directives.uses {
380 if use_.reexport.is_some() && use_.directive_visibility() <= max_visibility {
381 for path in use_.paths.values() {
382 reexports.push((path.clone(), use_.content.clone()));
383 }
384 }
385 text.extend(use_.get_md_text(3, max_visibility));
386 }
387 text.extend(Self::make_md_list(3, "Re-exports", &reexports));
388
389 for (name, item) in order_items(self.file_directives.items) {
390 text.extend(Self::make_md_section(
391 name,
392 fence_size,
393 item,
394 max_visibility,
395 ));
396 }
397 text.push(fence);
398
399 text
400 }
401
402 fn fence_size(&self) -> usize {
403 Self::calc_fence_size(&self.file_directives.items)
404 }
405}