use std::{
fs,
path::{Path, PathBuf},
};
use crate::{
error::{DoomError, Result},
paths::RepoContext,
sets::parse_set_token,
};
pub fn discover_local_source_candidates(repo: &RepoContext) -> Vec<PathBuf> {
let mut candidates = Vec::new();
let entries = match fs::read_dir(repo.root()) {
Ok(entries) => entries,
Err(_) => return candidates,
};
for entry in entries.filter_map(|entry| entry.ok()) {
let path = entry.path();
if !path.is_dir() {
continue;
}
let lower_name = path
.file_name()
.map(|value| value.to_string_lossy().to_ascii_lowercase())
.unwrap_or_default();
if lower_name == "warehouse" || path.join("warehouse").is_dir() {
candidates.push(path);
}
}
candidates.sort_by_key(|path| path.to_string_lossy().to_ascii_lowercase());
candidates.truncate(5);
candidates
}
pub fn resolve_source_warehouse(repo: &RepoContext, source_path: impl AsRef<Path>) -> Result<(PathBuf, PathBuf)> {
let resolved = repo.repo_path(source_path);
if !resolved.exists() {
let suggestions = discover_local_source_candidates(repo);
let mut hint = String::new();
if !suggestions.is_empty() {
hint.push_str("\nAvailable local source-pack candidates:\n");
hint.push_str(
&suggestions
.iter()
.map(|candidate| format!("- {}", candidate.display()))
.collect::<Vec<_>>()
.join("\n"),
);
}
return Err(DoomError::message(format!(
"Missing source path: {}{}",
resolved.display(),
hint
)));
}
let warehouse_path = resolved.join("warehouse");
if warehouse_path.is_dir() {
return Ok((warehouse_path, resolved));
}
if resolved.is_dir()
&& resolved
.file_name()
.map(|value| value.to_string_lossy().eq_ignore_ascii_case("warehouse"))
.unwrap_or(false)
{
let parent = resolved.parent().map(Path::to_path_buf).unwrap_or_else(|| resolved.clone());
return Ok((resolved, parent));
}
Err(DoomError::message(format!(
"Expected a folder that contains 'warehouse/' or a direct 'warehouse' folder. Got: {}",
resolved.display()
)))
}
pub fn detect_source_set(warehouse_root: &Path) -> Result<String> {
let slayer_dir = warehouse_root
.join("models")
.join("customization")
.join("characters")
.join("doomslayer");
if !slayer_dir.exists() {
return Err(DoomError::message(format!(
"Missing Slayer texture root: {}",
slayer_dir.display()
)));
}
let mut set_dirs = fs::read_dir(&slayer_dir)?
.filter_map(|entry| entry.ok())
.map(|entry| entry.path())
.filter(|path| path.is_dir())
.collect::<Vec<_>>();
set_dirs.sort_by_key(|path| path.to_string_lossy().to_ascii_lowercase());
for set_dir in set_dirs {
let Some(set_name) = set_dir
.file_name()
.and_then(|value| parse_set_token(value.to_string_lossy()))
else {
continue;
};
let torso = set_dir.join(format!("doomslayer_game_torso_{set_name}.tga"));
if torso.exists() {
return Ok(set_name);
}
}
Err(DoomError::message(format!(
"Could not detect a Slayer set from: {}",
slayer_dir.display()
)))
}