use std::path::{Path, PathBuf};
use crate::errors::{
missing_module_file_error, unknown_module_error, unparseable_source_error,
unreadable_source_error,
};
use crate::resolve::strip_raw;
use crate::syn_util::has_path_attr;
fn module_segments(module: &str) -> Vec<String> {
module
.split("::")
.map(strip_raw)
.enumerate()
.filter(|(i, seg)| !(*i == 0 && seg == "crate"))
.map(|(_, seg)| seg)
.filter(|seg| !seg.is_empty())
.collect()
}
pub(crate) fn resolve_module_items(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<Vec<syn::Item>, String> {
resolve_module(src_dir, root_file, module, crate_package).map(|(items, _file)| items)
}
pub(crate) fn resolve_module_file(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<PathBuf, String> {
resolve_module(src_dir, root_file, module, crate_package).map(|(_items, file)| file)
}
fn resolve_module(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<(Vec<syn::Item>, PathBuf), String> {
let root = read_parse(root_file)?;
let segments = module_segments(module);
descend(
root.items,
src_dir.to_path_buf(),
root_file.to_path_buf(),
&segments,
module,
crate_package,
)
}
fn descend(
items: Vec<syn::Item>,
child_dir: PathBuf,
current_file: PathBuf,
segments: &[String],
module: &str,
crate_package: &str,
) -> Result<(Vec<syn::Item>, PathBuf), String> {
let Some(seg) = segments.first() else {
return Ok((items, current_file));
};
for item in &items {
if let syn::Item::Mod(module_item) = item {
if has_path_attr(&module_item.attrs) {
continue;
}
if strip_raw(&module_item.ident.to_string()) != *seg {
continue;
}
match &module_item.content {
Some((_, inner)) => {
return descend(
inner.clone(),
child_dir.join(seg),
current_file,
&segments[1..],
module,
crate_package,
);
}
None => {
let file = locate_module_file(&child_dir, seg)
.ok_or_else(|| missing_module_file_error(module, crate_package))?;
let parsed = read_parse(&file)?;
return descend(
parsed.items,
child_dir.join(seg),
file,
&segments[1..],
module,
crate_package,
);
}
}
}
}
Err(unknown_module_error(module, crate_package))
}
pub(crate) fn locate_module_file(child_dir: &Path, seg: &str) -> Option<PathBuf> {
let flat = child_dir.join(format!("{seg}.rs"));
if flat.is_file() {
return Some(flat);
}
let nested = child_dir.join(seg).join("mod.rs");
if nested.is_file() {
return Some(nested);
}
None
}
pub(crate) fn read_parse(file: &Path) -> Result<syn::File, String> {
let text = std::fs::read_to_string(file)
.map_err(|err| unreadable_source_error(file, &err.to_string()))?;
syn::parse_file(&text).map_err(|err| unparseable_source_error(file, &err.to_string()))
}