use std::fs;
use std::path;
use anyhow::anyhow;
use crate::doc;
use crate::index;
use crate::parser::html;
pub trait Source {
fn find_doc(
&self,
name: &doc::Fqn,
ty: Option<doc::ItemType>,
) -> anyhow::Result<Option<doc::Doc>>;
fn load_index(&self) -> anyhow::Result<Option<index::Index>>;
}
pub struct Sources(Vec<Box<dyn Source>>);
#[derive(Clone, Debug, PartialEq)]
pub struct DirSource {
path: path::PathBuf,
}
impl Sources {
pub fn new(sources: Vec<Box<dyn Source>>) -> Sources {
Sources(sources)
}
pub fn find(
&self,
name: &doc::Name,
ty: Option<doc::ItemType>,
) -> anyhow::Result<Option<doc::Doc>> {
let fqn = name.clone().into();
for source in &self.0 {
if let Some(doc) = source.find_doc(&fqn, ty)? {
return Ok(Some(doc));
}
}
log::info!("Could not find item '{}'", fqn);
Ok(None)
}
pub fn search(&self, name: &doc::Name) -> anyhow::Result<Vec<index::IndexItem>> {
let indexes = self
.0
.iter()
.filter_map(|s| s.load_index().transpose())
.collect::<anyhow::Result<Vec<_>>>()?;
let mut items = indexes
.iter()
.map(|i| i.find(name))
.collect::<Vec<_>>()
.concat();
items.sort_unstable();
items.dedup();
Ok(items)
}
}
impl DirSource {
fn new(path: path::PathBuf) -> Self {
log::info!("Created directory source at '{}'", path.display());
Self { path }
}
fn find_doc_html(
&self,
path: &path::Path,
name: &doc::Fqn,
ty: Option<doc::ItemType>,
) -> anyhow::Result<Option<doc::Doc>> {
if let Some(ty) = ty {
match ty {
doc::ItemType::Module => self.get_module(&path, name),
doc::ItemType::StructField
| doc::ItemType::Variant
| doc::ItemType::AssocType
| doc::ItemType::AssocConst
| doc::ItemType::Method => self.get_member(&path, name),
_ => self.get_item(&path, name),
}
} else {
self.get_item(&path, name)
.transpose()
.or_else(|| self.get_module(&path, name).transpose())
.or_else(|| self.get_member(&path, name).transpose())
.transpose()
}
}
fn get_crate(&self, name: &str) -> Option<path::PathBuf> {
log::info!(
"Searching crate '{}' in dir source '{}'",
name,
self.path.display()
);
let crate_path = self.path.join(name.replace('-', "_"));
if crate_path.join("all.html").is_file() {
log::info!("Found crate '{}': '{}'", name, crate_path.display());
Some(crate_path)
} else {
log::info!("Did not find crate '{}' in '{}'", name, self.path.display());
None
}
}
fn get_item(&self, root: &path::Path, name: &doc::Fqn) -> anyhow::Result<Option<doc::Doc>> {
log::info!(
"Searching item '{}' in directory '{}'",
name,
root.display()
);
if let Some(local_name) = name.rest() {
let parser = html::Parser::from_file(root.join("all.html"))?;
if let Some(path) = parser.find_item(local_name)? {
let file_name = path::Path::new(&path)
.file_name()
.unwrap()
.to_str()
.unwrap();
let ty: doc::ItemType = file_name.splitn(2, '.').next().unwrap().parse()?;
html::Parser::from_file(root.join(path))?
.parse_item_doc(name, ty)
.map(Some)
} else {
Ok(None)
}
} else {
Ok(None)
}
}
fn get_module(&self, root: &path::Path, name: &doc::Fqn) -> anyhow::Result<Option<doc::Doc>> {
log::info!(
"Searching module '{}' in directory '{}'",
name,
root.display()
);
let module_path = if let Some(local_name) = name.rest() {
local_name
.split("::")
.fold(path::PathBuf::new(), |mut p, s| {
p.push(s);
p
})
} else {
path::PathBuf::new()
};
let path = root.join(module_path).join("index.html");
if path.is_file() {
html::Parser::from_file(path)?
.parse_module_doc(name)
.map(Some)
} else {
Ok(None)
}
}
fn get_member(&self, root: &path::Path, name: &doc::Fqn) -> anyhow::Result<Option<doc::Doc>> {
log::info!(
"Searching member '{}' in directory '{}'",
name,
root.display()
);
if let Some(parent) = name.parent() {
if let Some(rest) = parent.rest() {
let parser = html::Parser::from_file(root.join("all.html"))?;
if let Some(path) = parser.find_item(rest)? {
let parser = html::Parser::from_file(root.join(path))?;
if let Some(ty) = parser.find_member(name)? {
return parser.parse_member_doc(name, ty).map(Some);
}
}
}
}
Ok(None)
}
}
impl Source for DirSource {
fn find_doc(
&self,
name: &doc::Fqn,
ty: Option<doc::ItemType>,
) -> anyhow::Result<Option<doc::Doc>> {
log::info!(
"Searching documentation for '{}' in dir source '{}'",
name,
self.path.display()
);
if let Some(crate_path) = self.get_crate(name.krate()) {
let doc = self.find_doc_html(&crate_path, name, ty)?;
if doc.is_some() {
log::info!(
"Found documentation for '{}' in dir source '{}'",
name,
self.path.display()
)
} else {
log::info!(
"Did not find documentation for '{}' in dir source '{}'",
name,
self.path.display()
)
}
Ok(doc)
} else {
log::info!(
"Did not find crate '{}' in dir source '{}'",
name.krate(),
self.path.display()
);
Ok(None)
}
}
fn load_index(&self) -> anyhow::Result<Option<index::Index>> {
log::info!("Searching search index for '{}'", self.path.display());
for entry in fs::read_dir(&self.path)? {
let entry = entry?;
if entry.file_type()?.is_file() {
if let Some(s) = entry.file_name().to_str() {
if s.starts_with("search-index") && s.ends_with(".js") {
log::info!("Found search index '{}'", &entry.path().display());
return index::Index::load(&entry.path());
}
}
}
}
log::info!("Could not find search index for '{}'", self.path.display());
Ok(None)
}
}
pub fn get_source<P: AsRef<path::Path>>(path: P) -> anyhow::Result<Box<dyn Source>> {
if path.as_ref().is_dir() {
Ok(Box::new(DirSource::new(path.as_ref().to_path_buf())))
} else {
Err(anyhow!(
"This source is not supported: {}",
path.as_ref().display()
))
}
}