use std::cmp::max;
use std::collections::BTreeMap;
use std::path;
use std::path::{Path, PathBuf};
use quote::ToTokens;
use syn::{Expr, ItemMod, Meta};
use crate::directives::directive_options::{DirectiveOption, DirectiveVisibility, IndexEntryType};
use crate::directives::{
extract_doc_from_attrs,
order_items,
Directive,
FileDirectives,
ImplDirective,
UseDirective,
};
use crate::formats::{Format, MdContent, MdDirective, RstContent, RstDirective};
use crate::utils::{FileTopLevelDirective, SourceCodeFile};
use crate::RuntimeConfiguration;
#[derive(Clone, Debug)]
pub struct ModuleDirective {
pub(crate) name: String,
pub(crate) options: Vec<DirectiveOption>,
pub(crate) content: Vec<String>,
pub(crate) ident: String,
pub(crate) doc_file: PathBuf,
pub(crate) source_code_file: SourceCodeFile,
pub(crate) file_directives: FileDirectives,
}
#[inline]
fn has_test_token(tokens: &str) -> bool {
tokens.split(',').any(|t| t.trim() == "test")
}
fn find_file_under_dir(module_ident: &str, directory: &Path) -> Option<PathBuf> {
let mut mod_file = directory.join(format!("{module_ident}.rs"));
if mod_file.is_file() {
return Some(mod_file);
}
mod_file = directory.join(module_ident).join("mod.rs");
if mod_file.is_file() {
return Some(mod_file);
}
None
}
pub(crate) struct ModuleAttrs {
pub(crate) is_test: bool,
pub(crate) path: PathBuf,
}
impl ModuleAttrs {
pub(crate) fn parse_module_attrs(
item_mod: &ItemMod,
parent_file: &SourceCodeFile,
) -> ModuleAttrs {
let mut module_attrs = ModuleAttrs {
is_test: false,
path: Self::get_module_file(&item_mod.ident.to_string(), parent_file),
};
for attr in &item_mod.attrs {
match &attr.meta {
Meta::List(metalist) => {
if metalist.path.segments.len() == 1
&& metalist.path.segments.first().unwrap().ident == "cfg"
&& has_test_token(&metalist.tokens.to_string())
{
module_attrs.is_test = true
}
}
Meta::NameValue(meta_name_value) => {
if meta_name_value.path.segments.len() == 1
&& meta_name_value.path.segments.first().unwrap().ident == "path"
{
if let Expr::Lit(value) = &meta_name_value.value {
module_attrs.path = parent_file.path.join(Path::new(
&value.to_token_stream().to_string().trim_matches('"'),
));
}
else {
panic!(
"Expected path for module file to be a literal. Found {0:?}",
&meta_name_value.value
)
}
}
}
Meta::Path(_) => {}
}
}
module_attrs
}
fn get_module_file(module_ident: &str, parent_file: &SourceCodeFile) -> PathBuf {
if parent_file.path.is_dir() {
find_file_under_dir(module_ident, &parent_file.path)
.unwrap_or(parent_file.path.join(module_ident).join("mod.rs"))
}
else if parent_file.path.ends_with("mod.rs") {
let parent_dir = parent_file.path.parent().unwrap();
find_file_under_dir(module_ident, parent_dir)
.unwrap_or(parent_dir.join(module_ident).join("mod.rs"))
}
else {
let parent_dir = parent_file
.path
.parent()
.unwrap()
.join(parent_file.path.file_stem().unwrap());
find_file_under_dir(module_ident, &parent_dir)
.unwrap_or(parent_dir.join(module_ident).join("mod.rs"))
}
}
}
impl ModuleDirective {
const DIRECTIVE_NAME: &'static str = "module";
pub(crate) fn from_item(
rc: &RuntimeConfiguration,
parent_file: &SourceCodeFile,
item: &ItemMod,
) -> Option<Self> {
let module_attrs = ModuleAttrs::parse_module_attrs(item, parent_file);
if module_attrs.is_test {
return None;
}
let source_code_file = SourceCodeFile {
path: module_attrs.path,
item: format!("{}::{}", &parent_file.item, item.ident),
};
let mod_items = item.content.as_ref().map(|(_, items)| items);
let mut mod_attrs = item.attrs.clone();
let file_directives = match mod_items {
None => {
let ast = source_code_file.ast();
mod_attrs.extend(ast.attrs);
FileDirectives::from_ast_items(rc, &ast.items, &source_code_file)
}
Some(m) => FileDirectives::from_ast_items(rc, m, &source_code_file),
};
Some(ModuleDirective {
name: source_code_file.item.clone(),
options: vec![
DirectiveOption::Index(IndexEntryType::Normal),
DirectiveOption::Vis(DirectiveVisibility::from(&item.vis)),
],
content: extract_doc_from_attrs(&mod_attrs),
ident: item.ident.to_string(),
doc_file: rc.get_doc_file_name(&source_code_file.path),
source_code_file,
file_directives,
})
}
pub(crate) fn filter_items(&mut self, max_visibility: &DirectiveVisibility) -> Vec<Directive> {
let mut excluded_items = vec![];
self.file_directives.modules.retain_mut(|module| {
excluded_items.extend(module.filter_items(max_visibility));
module.directive_visibility() <= max_visibility
});
let directive_visibility = *self.directive_visibility();
let mut reexports = vec![];
for use_ in &mut self.file_directives.uses {
if use_.reexport.is_some() && use_.directive_visibility() <= max_visibility {
reexports.push(use_)
}
}
if &directive_visibility > max_visibility {
while let Some(item) = self.file_directives.items.pop() {
if item.directive_visibility() <= max_visibility {
excluded_items.push(item);
}
}
'item_loop: for item in excluded_items.iter_mut() {
for reexport in &reexports {
if reexport.contains(item.name()) {
item.change_parent(&self.name);
continue 'item_loop;
}
}
}
return excluded_items;
}
let mut not_documented = vec![];
let mut inlined = BTreeMap::new();
'item_loop: for mut item in excluded_items {
for reexport in &mut reexports {
if reexport.contains(item.name()) {
if !matches!(item, Directive::Impl(_)) {
let (k, v) = reexport.inline(item.name()).unwrap();
inlined.insert(k, v);
}
item.change_parent(&self.name);
item.add_content(reexport.content.clone());
self.file_directives.items.push(item);
continue 'item_loop;
}
}
not_documented.push(item);
}
self.file_directives
.uses
.push(UseDirective::for_use_paths(inlined));
not_documented
}
pub(crate) fn directive_visibility(&self) -> &DirectiveVisibility {
if let DirectiveOption::Vis(v) = &self.options[1] {
return v;
}
unreachable!("Module: order of options changed")
}
pub(crate) fn change_parent(&mut self, new_parent: &str) {
self.name = format!("{new_parent}::{}", self.ident);
for item in &mut self.file_directives.items {
item.change_parent(&self.name);
}
}
pub(crate) fn collect_impls(&mut self) -> Vec<ImplDirective> {
let mut impls = vec![];
impls.append(&mut self.file_directives.impls);
for module in &mut self.file_directives.modules {
impls.extend(module.collect_impls())
}
impls
}
pub(crate) fn claim_impls(&mut self, impls: Vec<ImplDirective>) -> Vec<ImplDirective> {
self.file_directives.claim_impls(&self.name, impls)
}
}
impl RstDirective for ModuleDirective {
fn get_rst_text(self, level: usize, max_visibility: &DirectiveVisibility) -> Vec<String> {
let content_indent = Self::make_content_indent(level);
let mut text =
Self::make_rst_header(Self::DIRECTIVE_NAME, &self.name, &self.options, level);
text.extend(self.content.get_rst_text(&content_indent));
let doc_file_parent = self.doc_file.parent().unwrap().to_str().unwrap();
text.extend(Self::make_rst_toctree(
&content_indent,
"Modules",
Some(1),
self.file_directives.modules.iter().map(|m| {
m.doc_file
.to_str()
.unwrap()
.trim_start_matches(doc_file_parent)
.trim_start_matches(path::MAIN_SEPARATOR)
}),
));
let mut reexports = vec![];
for use_ in self.file_directives.uses {
if use_.reexport.is_some() && use_.directive_visibility() <= max_visibility {
for path in use_.paths.values() {
reexports.push((path.clone(), use_.content.clone()));
}
}
text.extend(use_.get_rst_text(level + 1, max_visibility));
}
text.extend(Self::make_rst_list(
&content_indent,
"Re-exports",
&reexports,
));
for (name, item) in order_items(self.file_directives.items) {
text.extend(Self::make_rst_section(name, level, item, max_visibility));
}
text
}
}
impl MdDirective for ModuleDirective {
fn get_md_text(self, fence_size: usize, max_visibility: &DirectiveVisibility) -> Vec<String> {
let fence = Self::make_fence(max(fence_size, 4));
let mut text =
Self::make_md_header(Self::DIRECTIVE_NAME, &self.name, &self.options, &fence);
text.extend(self.content.get_md_text());
let doc_file_parent = self.doc_file.parent().unwrap().to_str().unwrap();
text.extend(Self::make_md_toctree(
3,
"Modules",
Some(1),
self.file_directives.modules.iter().map(|m| {
m.doc_file
.to_str()
.unwrap()
.trim_start_matches(doc_file_parent)
.trim_start_matches(path::MAIN_SEPARATOR)
}),
));
let mut reexports = vec![];
for use_ in self.file_directives.uses {
if use_.reexport.is_some() && use_.directive_visibility() <= max_visibility {
for path in use_.paths.values() {
reexports.push((path.clone(), use_.content.clone()));
}
}
text.extend(use_.get_md_text(3, max_visibility));
}
text.extend(Self::make_md_list(3, "Re-exports", &reexports));
for (name, item) in order_items(self.file_directives.items) {
text.extend(Self::make_md_section(
name,
fence_size,
item,
max_visibility,
));
}
text.push(fence);
text
}
fn fence_size(&self) -> usize {
Self::calc_fence_size(&self.file_directives.items)
}
}
impl FileTopLevelDirective for ModuleDirective {
fn get_doc_file(&self) -> &Path {
&self.doc_file
}
fn get_text(self, format: &Format, max_visibility: &DirectiveVisibility) -> Vec<String> {
let mut text = format.make_title(&format.make_inline_code(format!("mod {}", self.ident)));
text.extend(format.format_directive(self, max_visibility));
text
}
}