use std::path::{Path, PathBuf};
use crate::error::{Result, ServerError};
pub(crate) struct QueryFile {
pub resolved: PathBuf,
pub hash: [u8; 32],
pub imports: Vec<String>,
pub source: String,
}
pub(crate) fn read_query_file(root: &Path, path: &Path) -> Result<QueryFile> {
let resolved = resolve_path(root, path).canonicalize().map_err(ServerError::Io)?;
let bytes = std::fs::read(&resolved).map_err(ServerError::Io)?;
let hash = crate::cache::hash_bytes(&bytes);
let source = String::from_utf8(bytes).map_err(|e| ServerError::Internal(format!("file not UTF-8: {e}")))?;
let imports = header_imports(&source);
Ok(QueryFile {
resolved,
hash,
imports,
source,
})
}
pub(crate) fn resolve_path(root: &Path, path: &Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else {
root.join(path)
}
}
pub(crate) fn module_name_for_file(root: &Path, path: &Path) -> Option<String> {
let relative = path.strip_prefix(root).ok()?;
if relative.extension()? != "lean" {
return None;
}
let stemmed = relative.with_extension("");
let parts = stemmed
.components()
.map(|component| component.as_os_str().to_str())
.collect::<Option<Vec<_>>>()?;
if parts.is_empty() { None } else { Some(parts.join(".")) }
}
pub(crate) fn header_imports(source: &str) -> Vec<String> {
source
.lines()
.filter_map(|line| {
let line = line.split_once("--").map_or(line, |(before, _)| before);
let mut words = line.split_whitespace();
let mut token = words.next()?;
if token == "public" {
token = words.next()?;
}
if token == "meta" {
token = words.next()?;
}
if token != "import" {
return None;
}
if words.clone().next() == Some("all") {
let _ = words.next();
}
words.next().map(str::to_owned)
})
.collect()
}