use std::{
fs::read as read_file,
io::Result as IoResult,
path::{Path, PathBuf},
};
use lune_utils::path::{
LuauModulePath, clean_path_and_make_absolute,
constants::{FILE_CHUNK_PREFIX, FILE_NAME_CONFIG},
relative_path_normalize, relative_path_parent,
};
use mlua::prelude::*;
use super::loader::RequireLoader;
#[derive(Debug)]
pub(crate) struct RequireResolver {
absolute: PathBuf,
relative: PathBuf,
resolved: Option<LuauModulePath>,
loader: RequireLoader,
}
impl RequireResolver {
pub(crate) fn new() -> Self {
Self {
relative: PathBuf::new(),
absolute: PathBuf::new(),
resolved: None,
loader: RequireLoader::new(),
}
}
fn navigate_reset(&mut self) {
self.relative = PathBuf::new();
self.absolute = PathBuf::new();
self.resolved = None;
}
fn navigate_to(
&mut self,
relative: PathBuf,
absolute: PathBuf,
) -> Result<(), LuaNavigateError> {
if self.relative == relative && self.absolute == absolute {
return Ok(());
}
let resolved = LuauModulePath::resolve(&absolute)?;
self.absolute = absolute;
self.relative = relative;
self.resolved = Some(resolved);
Ok(())
}
}
impl LuaRequire for RequireResolver {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with(FILE_CHUNK_PREFIX)
}
fn reset(&mut self, chunk_name: &str) -> Result<(), LuaNavigateError> {
self.navigate_reset();
if let Some(path) = chunk_name.strip_prefix(FILE_CHUNK_PREFIX) {
let rel = relative_path_normalize(Path::new(path));
let abs = clean_path_and_make_absolute(&rel);
self.navigate_to(rel, abs)
} else {
Err(LuaNavigateError::Other(LuaError::runtime(
"cannot reset require state from non-file chunk",
)))
}
}
fn jump_to_alias(&mut self, path: &str) -> Result<(), LuaNavigateError> {
let rel = relative_path_normalize(Path::new(path));
let abs = clean_path_and_make_absolute(&rel);
self.navigate_to(rel, abs)
}
fn to_parent(&mut self) -> Result<(), LuaNavigateError> {
let mut rel = self.relative.clone();
let mut abs = self.absolute.clone();
if abs.pop() {
relative_path_parent(&mut rel);
self.navigate_to(rel, abs)
} else {
Err(LuaNavigateError::NotFound)
}
}
fn to_child(&mut self, name: &str) -> Result<(), LuaNavigateError> {
let rel = self.relative.join(name);
let abs = self.absolute.join(name);
self.navigate_to(rel, abs)
}
fn has_module(&self) -> bool {
let resolved = self.resolved.as_ref();
resolved.is_some_and(|p| p.target().is_file())
}
fn cache_key(&self) -> String {
let resolved = self.resolved.as_ref();
resolved.expect("called has_module first").to_string()
}
fn has_config(&self) -> bool {
self.absolute.is_dir() && self.absolute.join(FILE_NAME_CONFIG).is_file()
}
fn config(&self) -> IoResult<Vec<u8>> {
read_file(self.absolute.join(FILE_NAME_CONFIG))
}
fn loader(&self, lua: &Lua) -> LuaResult<LuaFunction> {
let resolved = self.resolved.as_ref();
let resolved = resolved.expect("called has_module first");
let resolved = resolved.target().as_file().expect("tried to require a dir");
self.loader.load(lua, self.relative.as_path(), resolved)
}
}