use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use super::super::error::{ClosureSite, SourceClosureFailure, SourceClosureFailureKind};
use super::super::failure::failure;
use super::super::store::SourceStore;
use super::walk::ChildContext;
pub(super) fn inline_directories(context: &ChildContext<'_>) -> Box<[PathBuf]> {
match &*context.declaration.declared_paths {
[] => Box::from([context
.frame
.directory
.join(module_file_name(&context.declaration.name))]),
[first, rest @ ..] => declared_directories(context, (&**first, rest)),
}
}
fn declared_directories(
context: &ChildContext<'_>,
declared: (&str, &[Arc<str>]),
) -> Box<[PathBuf]> {
let (first, rest) = declared;
let shipped: Box<[PathBuf]> = std::iter::once(first)
.chain(rest.iter().map(|path| &**path))
.map(|path| context.frame.directory.join(path))
.filter(|directory| directory.is_dir())
.collect();
match shipped.is_empty() {
true => Box::from([context.frame.directory.join(first)]),
false => shipped,
}
}
pub(super) fn candidate_paths(
store: &SourceStore,
context: &ChildContext<'_>,
site: &ClosureSite,
) -> Result<Box<[PathBuf]>, SourceClosureFailure> {
match &*context.declaration.declared_paths {
[] => standard_candidate(store, context, site).map(|path| Box::from([path])),
[declared] => {
declared_candidate(store, context, (site, declared)).map(|path| Box::from([path]))
}
alternatives => declared_alternatives(store, context, (site, alternatives)),
}
}
fn declared_candidate(
store: &SourceStore,
context: &ChildContext<'_>,
request: (&ClosureSite, &str),
) -> Result<PathBuf, SourceClosureFailure> {
let (site, declared) = request;
let candidate = context.frame.declared_directory.join(declared);
match candidate.is_file() {
true => Ok(candidate),
false => Err(missing(store, site, &candidate)),
}
}
fn declared_alternatives(
store: &SourceStore,
context: &ChildContext<'_>,
request: (&ClosureSite, &[Arc<str>]),
) -> Result<Box<[PathBuf]>, SourceClosureFailure> {
let (site, alternatives) = request;
let reachable: Box<[PathBuf]> = alternatives
.iter()
.map(|declared| context.frame.declared_directory.join(&**declared))
.filter(|candidate| reachable_alternative(store, candidate, site))
.collect();
match reachable.first() {
Some(_) => Ok(reachable),
None => Err(missing(
store,
site,
&context.frame.declared_directory.join(&*alternatives[0]),
)),
}
}
fn reachable_alternative(store: &SourceStore, candidate: &Path, site: &ClosureSite) -> bool {
candidate.is_file() && store.canonical_inside(candidate, site).is_ok()
}
fn standard_candidate(
store: &SourceStore,
context: &ChildContext<'_>,
site: &ClosureSite,
) -> Result<PathBuf, SourceClosureFailure> {
let name = module_file_name(&context.declaration.name);
let flat = context.frame.directory.join(format!("{name}.rs"));
let nested = context.frame.directory.join(name).join("mod.rs");
match (flat.is_file(), nested.is_file()) {
(true, true) => Err(ambiguous(store, site, &flat)),
(true, false) => Ok(flat),
(false, true) => Ok(nested),
(false, false) => Err(missing(store, site, &flat)),
}
}
fn missing(store: &SourceStore, site: &ClosureSite, attempted: &Path) -> SourceClosureFailure {
failure(
SourceClosureFailureKind::MissingModule,
(site, store.relative_of(attempted)),
format!("{site}: no source exists for the declared module"),
)
}
fn ambiguous(store: &SourceStore, site: &ClosureSite, attempted: &Path) -> SourceClosureFailure {
failure(
SourceClosureFailureKind::AmbiguousModule,
(site, store.relative_of(attempted)),
format!("{site}: both name.rs and name/mod.rs exist"),
)
}
pub(super) fn file_directory(file: &Path) -> PathBuf {
file.parent().map(Path::to_path_buf).unwrap_or_default()
}
pub(super) fn child_directory(file: &Path, crate_root: bool) -> PathBuf {
let parent = file_directory(file);
let stem = file.file_stem().and_then(OsStr::to_str).unwrap_or_default();
match crate_root || stem == "mod" {
true => parent,
false => parent.join(stem),
}
}
fn module_file_name(name: &str) -> &str {
name.strip_prefix("r#").unwrap_or(name)
}