use std::collections::HashMap;
use std::path::Path;
use rustdoc_types::{Crate, Id, Impl, Item, ItemEnum, Visibility};
use crate::Args;
use crate::generator::config::RenderConfig;
use crate::generator::doc_links::{DocLinkProcessor, DocLinkUtils};
use crate::generator::render_shared::SourcePathConfig;
use crate::linker::LinkRegistry;
pub trait ItemAccess {
fn krate(&self) -> &Crate;
fn crate_name(&self) -> &str;
fn get_item(&self, id: &Id) -> Option<&Item>;
fn get_impls(&self, id: &Id) -> Option<&[&Impl]>;
fn crate_version(&self) -> Option<&str>;
fn render_config(&self) -> &RenderConfig;
fn source_path_config_for_file(&self, _current_file: &str) -> Option<SourcePathConfig> {
None
}
}
pub trait ItemFilter {
fn should_include_item(&self, item: &Item) -> bool;
fn include_private(&self) -> bool;
fn include_blanket_impls(&self) -> bool;
}
pub trait LinkResolver {
fn link_registry(&self) -> Option<&LinkRegistry>;
fn process_docs(&self, item: &Item, current_file: &str) -> Option<String>;
fn create_link(&self, id: Id, current_file: &str) -> Option<String>;
}
pub trait RenderContext: ItemAccess + ItemFilter + LinkResolver {}
pub struct GeneratorContext<'a> {
pub krate: &'a Crate,
crate_name: String,
pub impl_map: HashMap<Id, Vec<&'a Impl>>,
pub link_registry: LinkRegistry,
pub args: &'a Args,
pub config: RenderConfig,
path_name_index: HashMap<&'a str, Vec<Id>>,
source_path_config: Option<SourcePathConfig>,
}
impl<'a> GeneratorContext<'a> {
#[must_use]
pub fn new(krate: &'a Crate, args: &'a Args, config: RenderConfig) -> Self {
use crate::CliOutputFormat;
let crate_name = krate
.index
.get(&krate.root)
.and_then(|item| item.name.clone())
.unwrap_or_else(|| "unnamed".to_string());
let impl_map = Self::build_impl_map(krate);
let is_flat = matches!(args.format, CliOutputFormat::Flat);
let link_registry = LinkRegistry::build(krate, is_flat, !args.exclude_private);
let path_name_index = Self::build_path_name_index(krate);
let source_path_config = if config.include_source.source_locations {
config
.include_source
.source_dir
.as_ref()
.map(|dir| SourcePathConfig::new(dir, ""))
} else {
None
};
Self {
krate,
crate_name,
impl_map,
link_registry,
args,
config,
path_name_index,
source_path_config,
}
}
pub fn set_source_dir(&mut self, source_dir: &Path) {
if self.config.include_source.source_locations {
self.source_path_config = Some(SourcePathConfig::new(source_dir, ""));
}
}
fn build_impl_map(krate: &'a Crate) -> HashMap<Id, Vec<&'a Impl>> {
let mut map: HashMap<Id, Vec<&'a Impl>> = HashMap::new();
for (type_id, item) in &krate.index {
let impl_ids: &[Id] = match &item.inner {
ItemEnum::Struct(s) => &s.impls,
ItemEnum::Enum(e) => &e.impls,
ItemEnum::Union(u) => &u.impls,
_ => continue,
};
for impl_id in impl_ids {
if let Some(impl_item) = krate.index.get(impl_id)
&& let ItemEnum::Impl(impl_block) = &impl_item.inner
{
map.entry(*type_id).or_default().push(impl_block);
}
}
}
for impls in map.values_mut() {
impls.sort_by(|a, b| Self::impl_sort_key(a).cmp(&Self::impl_sort_key(b)));
}
map
}
fn impl_sort_key(impl_block: &Impl) -> (u8, String) {
impl_block
.trait_
.as_ref()
.map_or_else(|| (0, String::new()), |path| (1, path.path.clone()))
}
#[must_use]
pub const fn should_include_item(&self, item: &Item) -> bool {
match &item.visibility {
Visibility::Public => true,
_ => !self.args.exclude_private,
}
}
#[must_use]
pub fn count_modules(&self, item: &Item) -> usize {
let mut count = 0;
if let ItemEnum::Module(module) = &item.inner {
for item_id in &module.items {
if let Some(child) = self.krate.index.get(item_id)
&& let ItemEnum::Module(_) = &child.inner
&& self.should_include_item(child)
{
count += 1;
count += self.count_modules(child);
}
}
}
count
}
fn build_path_name_index(krate: &'a Crate) -> HashMap<&'a str, Vec<Id>> {
let mut index: HashMap<&'a str, Vec<Id>> = HashMap::new();
for (id, path_info) in &krate.paths {
if let Some(name) = path_info.path.last() {
index.entry(name.as_str()).or_default().push(*id);
}
}
for ids in index.values_mut() {
ids.sort_by(|a, b| {
let path_a = krate.paths.get(a).map(|p| &p.path);
let path_b = krate.paths.get(b).map(|p| &p.path);
path_a.cmp(&path_b)
});
}
index
}
}
impl ItemAccess for GeneratorContext<'_> {
fn krate(&self) -> &Crate {
self.krate
}
fn crate_name(&self) -> &str {
&self.crate_name
}
fn get_item(&self, id: &Id) -> Option<&Item> {
self.krate.index.get(id)
}
fn get_impls(&self, id: &Id) -> Option<&[&Impl]> {
self.impl_map.get(id).map(Vec::as_slice)
}
fn crate_version(&self) -> Option<&str> {
self.krate.crate_version.as_deref()
}
fn render_config(&self) -> &RenderConfig {
&self.config
}
fn source_path_config_for_file(&self, current_file: &str) -> Option<SourcePathConfig> {
self.source_path_config
.as_ref()
.map(|base| base.with_depth(current_file))
}
}
impl ItemFilter for GeneratorContext<'_> {
fn should_include_item(&self, item: &Item) -> bool {
match &item.visibility {
Visibility::Public => true,
_ => !self.args.exclude_private,
}
}
fn include_private(&self) -> bool {
!self.args.exclude_private
}
fn include_blanket_impls(&self) -> bool {
self.args.include_blanket_impls
}
}
impl LinkResolver for GeneratorContext<'_> {
fn link_registry(&self) -> Option<&LinkRegistry> {
Some(&self.link_registry)
}
fn process_docs(&self, item: &Item, current_file: &str) -> Option<String> {
let docs = item.docs.as_ref()?;
let name = item.name.as_deref().unwrap_or("");
let docs = DocLinkUtils::strip_duplicate_title(docs, name);
let processor = DocLinkProcessor::with_index(
self.krate,
&self.link_registry,
current_file,
&self.path_name_index,
);
Some(processor.process(docs, &item.links))
}
fn create_link(&self, id: Id, current_file: &str) -> Option<String> {
self.link_registry.create_link(id, current_file)
}
}
impl<T: ItemAccess + ItemFilter + LinkResolver> RenderContext for T {}