use fieldwork::Fieldwork;
use rustdoc_types::{Crate, ExternalCrate, Id, Item, ItemKind};
use semver::{Version, VersionReq};
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use std::ops::Deref;
use std::path::PathBuf;
use crate::CrateProvenance;
use crate::doc_ref::{self, DocRef};
use crate::navigator::{Navigator, parse_docsrs_url};
#[derive(Clone, Fieldwork, PartialEq, Eq)]
#[fieldwork(get, rename_predicates)]
pub struct RustdocData {
pub(crate) crate_data: Crate,
pub(crate) name: String,
pub(crate) provenance: CrateProvenance,
pub(crate) fs_path: PathBuf,
pub(crate) version: Option<Version>,
#[field = false]
pub(crate) path_to_id: HashMap<String, Id>,
}
impl Debug for RustdocData {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("RustdocData")
.field("name", &self.name)
.field("crate_type", &self.provenance)
.field("fs_path", &self.fs_path)
.field("version", &self.version)
.finish()
}
}
impl Deref for RustdocData {
type Target = Crate;
fn deref(&self) -> &Self::Target {
&self.crate_data
}
}
impl RustdocData {
pub(crate) fn get<'a>(&'a self, navigator: &'a Navigator, id: &Id) -> Option<DocRef<'a, Item>> {
let item = self.crate_data.index.get(id)?;
Some(DocRef::new(navigator, self, item))
}
pub fn path<'a>(&'a self, id: &Id) -> Option<doc_ref::Path<'a>> {
self.paths.get(id).map(|summary| summary.into())
}
pub fn root_item<'a>(&'a self, navigator: &'a Navigator) -> DocRef<'a, Item> {
DocRef::new(navigator, self, &self.index[&self.root])
}
pub fn traverse_to_crate_by_id<'a>(
&'a self,
navigator: &'a Navigator,
id: u32,
) -> Option<&'a RustdocData> {
if id == 0 {
return Some(self);
}
let ExternalCrate {
name,
html_root_url,
..
} = self.external_crates.get(&id)?;
let (name, version_req) = html_root_url.as_deref().and_then(parse_docsrs_url).map_or(
(&**name, VersionReq::STAR),
|(name, version)| {
let version_req =
VersionReq::parse(&format!("={version}")).unwrap_or(VersionReq::STAR);
(name, version_req)
},
);
navigator.load_crate(name, &version_req)
}
pub(crate) fn get_path<'a>(
&'a self,
navigator: &'a Navigator,
id: Id,
) -> Option<DocRef<'a, Item>> {
let item_summary = self.paths.get(&id)?;
let crate_ = self.traverse_to_crate_by_id(navigator, item_summary.crate_id)?;
crate_
.root_item(navigator)
.find_by_path(item_summary.path.iter().skip(1))
}
pub(crate) fn build_path_index(&mut self) {
let mut by_unqualified: HashMap<String, Vec<(Id, ItemKind)>> = HashMap::new();
for (id, summary) in &self.crate_data.paths {
if summary.crate_id != 0 {
continue;
}
let Some(tail) = summary.path.get(1..) else {
continue;
};
if tail.is_empty() {
continue;
}
by_unqualified
.entry(tail.join("::"))
.or_default()
.push((*id, summary.kind));
}
let mut map = HashMap::new();
for (unqualified, items) in &by_unqualified {
let (prefix, last_name) = match unqualified.rfind("::") {
Some(sep) => (&unqualified[..sep + 2], &unqualified[sep + 2..]),
None => ("", unqualified.as_str()),
};
for (id, kind) in items {
let qualified = format!("{prefix}{}@{last_name}", kind_discriminator(*kind));
map.insert(qualified, *id);
}
if items.len() == 1 {
map.insert(unqualified.clone(), items[0].0);
}
}
self.path_to_id = map;
}
}
pub(crate) fn kind_discriminator(kind: ItemKind) -> &'static str {
match kind {
ItemKind::Module => "mod",
ItemKind::Struct => "struct",
ItemKind::Enum => "enum",
ItemKind::Union => "union",
ItemKind::Trait => "trait",
ItemKind::TraitAlias => "traitalias",
ItemKind::Function => "fn",
ItemKind::TypeAlias => "tyalias",
ItemKind::AssocType => "type",
ItemKind::Constant | ItemKind::AssocConst => "const",
ItemKind::Static => "static",
ItemKind::Macro => "macro",
ItemKind::ProcAttribute => "attr",
ItemKind::ProcDerive => "derive",
ItemKind::Primitive => "prim",
ItemKind::Variant => "variant",
ItemKind::StructField => "field",
ItemKind::Keyword => "keyword",
ItemKind::Attribute => "attribute",
ItemKind::ExternCrate | ItemKind::Use | ItemKind::Impl | ItemKind::ExternType => "item",
}
}