use std::{env, path::PathBuf, io::ErrorKind, fmt::Debug};
use super::*;
pub type ModuleResolveResult = Result<RantProgram, ModuleResolveError>;
pub trait ModuleResolver: Debug {
fn try_resolve(&self, context: &mut Rant, module_path: &str, dependant: Option<&RantProgramInfo>) -> ModuleResolveResult;
}
#[derive(Debug)]
pub struct DefaultModuleResolver {
pub enable_global_modules: bool,
pub local_modules_path: Option<String>,
}
impl DefaultModuleResolver {
pub const ENV_MODULES_PATH_KEY: &'static str = "RANT_MODULES_PATH";
}
impl Default for DefaultModuleResolver {
fn default() -> Self {
Self {
enable_global_modules: true,
local_modules_path: None,
}
}
}
impl ModuleResolver for DefaultModuleResolver {
fn try_resolve(&self, context: &mut Rant, module_path: &str, dependant: Option<&RantProgramInfo>) -> ModuleResolveResult {
if let Some(full_module_path) = self.find_module_path(module_path, dependant) {
let mut errors = vec![];
let compile_result = context.compile_file(full_module_path, &mut errors);
match compile_result {
Ok(module) => Ok(module),
Err(err) => {
Err(ModuleResolveError {
name: module_path.to_owned(),
reason: match err{
CompilerError::SyntaxError => {
ModuleResolveErrorReason::CompileFailed(errors)
},
CompilerError::IOError(ioerr) => {
match ioerr {
IOErrorKind::NotFound => {
ModuleResolveErrorReason::NotFound
},
_ => ModuleResolveErrorReason::FileIOError(ioerr)
}
}
}
})
}
}
} else {
Err(ModuleResolveError {
name: module_path.to_owned(),
reason: ModuleResolveErrorReason::NotFound,
})
}
}
}
impl DefaultModuleResolver {
#[inline]
fn find_module_path(&self, module_path: &str, dependant: Option<&RantProgramInfo>) -> Option<PathBuf> {
let module_path = PathBuf::from(
module_path.replace("/", &String::from(std::path::MAIN_SEPARATOR))
)
.with_extension(RANT_FILE_EXTENSION);
macro_rules! search_for_module {
($path:expr) => {
let path = $path;
if let Ok(full_module_path) = path
.join(&module_path)
.canonicalize()
{
if full_module_path.starts_with(path)
&& full_module_path.exists()
{
return Some(full_module_path)
}
}
}
}
if let Some(dependant_path) = dependant.map(|d| d.path.as_deref()) {
if let Some(program_path) =
dependant_path
.map(PathBuf::from)
.as_deref()
.and_then(|p| p.parent())
{
search_for_module!(program_path);
}
}
if let Some(local_modules_path) =
self.local_modules_path
.as_ref()
.map(PathBuf::from)
.or_else(||
env::current_dir()
.ok()
)
.and_then(|p| p.canonicalize().ok())
{
search_for_module!(local_modules_path);
}
if self.enable_global_modules {
if let Some(global_modules_path) =
env::var_os(Self::ENV_MODULES_PATH_KEY)
.map(PathBuf::from)
.and_then(|p| p.canonicalize().ok())
{
search_for_module!(global_modules_path);
}
}
None
}
}
#[derive(Debug)]
pub struct NoModuleResolver;
impl ModuleResolver for NoModuleResolver {
fn try_resolve(&self, _context: &mut Rant, module_path: &str, _dependant: Option<&RantProgramInfo>) -> ModuleResolveResult {
Err(ModuleResolveError {
name: module_path.to_owned(),
reason: ModuleResolveErrorReason::NotFound,
})
}
}
#[derive(Debug)]
pub struct ModuleResolveError {
pub name: String,
pub reason: ModuleResolveErrorReason,
}
impl Error for ModuleResolveError {}
impl ModuleResolveError {
#[inline]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn reason(&self) -> &ModuleResolveErrorReason {
&self.reason
}
}
#[derive(Debug)]
pub enum ModuleResolveErrorReason {
NotFound,
CompileFailed(Vec<CompilerMessage>),
FileIOError(ErrorKind),
}
impl Display for ModuleResolveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.reason() {
ModuleResolveErrorReason::NotFound => write!(f, "module '{}' not found", self.name()),
ModuleResolveErrorReason::CompileFailed(msgs) => write!(f, "module '{}' failed to compile: {}",
self.name(),
msgs.iter().fold(String::new(), |mut acc, msg| {
acc.push_str(&format!("[{}] {}\n", msg.severity(), msg.message()));
acc
})),
ModuleResolveErrorReason::FileIOError(ioerr) => write!(f, "file I/O error ({:?})", ioerr),
}
}
}
impl IntoRuntimeResult<RantProgram> for ModuleResolveResult {
fn into_runtime_result(self) -> RuntimeResult<RantProgram> {
self.map_err(|err| RuntimeError {
error_type: RuntimeErrorType::ModuleError(err),
description: None,
stack_trace: None,
})
}
}