use std::path::Path;
use rustdoc_types::ItemEnum;
use walkdir::WalkDir;
use crate::error::Error;
use crate::multi_crate::CrateCollection;
use crate::parser::Parser;
pub struct MultiCrateParser;
impl MultiCrateParser {
pub fn parse_directory(dir: &Path) -> Result<CrateCollection, Error> {
if !dir.is_dir() {
return Err(Error::InvalidDirectory(dir.display().to_string()));
}
let mut collection = CrateCollection::new();
for entry in WalkDir::new(dir)
.max_depth(1)
.into_iter()
.filter_map(Result::ok)
{
let path = entry.path();
if path.extension().is_some_and(|ext| ext != "json") {
continue;
}
if path.is_dir() {
continue;
}
let Ok(krate) = Parser::parse_file(path) else {
continue;
};
if !krate.index.contains_key(&krate.root) {
continue;
}
let crate_name = Self::extract_crate_name(&krate, path)?;
if collection.contains(&crate_name) {
return Err(Error::DuplicateCrate(crate_name));
}
collection.insert(crate_name, krate);
}
if collection.is_empty() {
return Err(Error::NoJsonFiles(dir.to_path_buf()));
}
Ok(collection)
}
fn extract_crate_name(krate: &rustdoc_types::Crate, path: &Path) -> Result<String, Error> {
let root_item = krate
.index
.get(&krate.root)
.ok_or_else(|| Error::NoCrateName(path.to_path_buf()))?;
if !matches!(&root_item.inner, ItemEnum::Module(_)) {
return Err(Error::NoCrateName(path.to_path_buf()));
}
root_item
.name
.clone()
.ok_or_else(|| Error::NoCrateName(path.to_path_buf()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_directory() {
let result = MultiCrateParser::parse_directory(Path::new("/nonexistent/path"));
assert!(matches!(result, Err(Error::InvalidDirectory(_))));
}
#[test]
fn test_empty_directory() {
let temp_dir = std::env::temp_dir().join("docs_md_test_empty");
let _ = std::fs::create_dir_all(&temp_dir);
let result = MultiCrateParser::parse_directory(&temp_dir);
assert!(matches!(result, Err(Error::NoJsonFiles(_))));
let _ = std::fs::remove_dir_all(&temp_dir);
}
}