use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
const SPECS_ROOT: &str = "../../comms/specs";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElementType {
Paragraph,
List,
Session,
Definition,
Annotation,
Verbatim,
Table,
Document,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DocumentType {
Benchmark,
Trifecta,
}
impl ElementType {
pub fn dir_name(&self) -> &'static str {
match self {
ElementType::Paragraph => "paragraph",
ElementType::List => "list",
ElementType::Session => "session",
ElementType::Definition => "definition",
ElementType::Annotation => "annotation",
ElementType::Verbatim => "verbatim",
ElementType::Table => "table",
ElementType::Document => "document",
}
}
}
impl DocumentType {
pub fn dir_name(&self) -> &'static str {
match self {
DocumentType::Benchmark => "benchmark",
DocumentType::Trifecta => "trifecta",
}
}
}
#[derive(Debug, Clone)]
pub enum SpecFileError {
FileNotFound(String),
IoError(String),
DuplicateNumber(String),
}
impl std::fmt::Display for SpecFileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SpecFileError::FileNotFound(msg) => write!(f, "File not found: {msg}"),
SpecFileError::IoError(msg) => write!(f, "IO error: {msg}"),
SpecFileError::DuplicateNumber(msg) => write!(f, "Duplicate number: {msg}"),
}
}
}
impl std::error::Error for SpecFileError {}
impl From<std::io::Error> for SpecFileError {
fn from(err: std::io::Error) -> Self {
SpecFileError::IoError(err.to_string())
}
}
pub fn get_doc_root(category: &str, subcategory: Option<&str>) -> PathBuf {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let crate_root = std::path::Path::new(manifest_dir);
let mut path = crate_root.join(SPECS_ROOT);
path.push(category);
if let Some(subcat) = subcategory {
if category == "elements" {
path.push(format!("{subcat}.docs"));
} else {
path.push(subcat);
}
}
path
}
pub fn list_files_by_number(dir: &PathBuf) -> Result<HashMap<usize, PathBuf>, SpecFileError> {
let mut number_map: HashMap<usize, PathBuf> = HashMap::new();
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if !path.extension().map(|e| e == "lex").unwrap_or(false) {
continue;
}
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
for part in filename.split('-') {
if let Ok(num) = part.parse::<usize>() {
if let Some(existing_path) = number_map.get(&num) {
panic!(
"DUPLICATE TEST NUMBERS DETECTED!\n\
Number {} found in multiple files:\n\
- {}\n\
- {}\n\n\
ERROR: Test numbers must be unique within each directory.\n\
FIX: Rename the duplicate files to use unique numbers.\n\
Directory: {}",
num,
existing_path.display(),
path.display(),
dir.display()
);
}
number_map.insert(num, path);
break; }
}
}
}
Ok(number_map)
}
pub fn find_specfile_by_number(
category: &str,
subcategory: Option<&str>,
number: usize,
) -> Result<PathBuf, SpecFileError> {
let dir = get_doc_root(category, subcategory);
for entry in fs::read_dir(&dir)? {
let entry = entry?;
let path = entry.path();
if !path.extension().map(|e| e == "lex").unwrap_or(false) {
continue;
}
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
for part in filename.split('-') {
if let Ok(num) = part.parse::<usize>() {
if num == number {
return Ok(path);
}
break; }
}
}
}
let location = if let Some(subcat) = subcategory {
format!("{category}/{subcat}")
} else {
category.to_string()
};
Err(SpecFileError::FileNotFound(format!(
"No file with number {number} found in {location}"
)))
}
pub fn list_available_numbers(
category: &str,
subcategory: Option<&str>,
) -> Result<Vec<usize>, SpecFileError> {
let dir = get_doc_root(category, subcategory);
let number_map = list_files_by_number(&dir)?;
let mut numbers: Vec<usize> = number_map.keys().copied().collect();
numbers.sort_unstable();
Ok(numbers)
}
pub fn find_element_file(
element_type: ElementType,
number: usize,
) -> Result<PathBuf, SpecFileError> {
find_specfile_by_number("elements", Some(element_type.dir_name()), number)
}
pub fn find_document_file(doc_type: DocumentType, number: usize) -> Result<PathBuf, SpecFileError> {
find_specfile_by_number(doc_type.dir_name(), None, number)
}
pub fn list_element_numbers(element_type: ElementType) -> Result<Vec<usize>, SpecFileError> {
list_available_numbers("elements", Some(element_type.dir_name()))
}