use std::{fs::{DirEntry, self}, io::ErrorKind}; use anyhow::anyhow; use log::{debug, warn, info}; use mlua::{Lua, Table}; use crate::QC_VERSION; fn load_plugin<'lua>(entry: &DirEntry, lua: &'lua Lua) -> anyhow::Result<(String, Table<'lua>)> { let ty = entry.file_type()?; let mut path = entry.path(); if ty.is_dir() { path.push("main.lua"); } let chunk = lua.load(path); debug!("evaluating plugin"); let table: Table = chunk.eval()?; let id = table.get("id") .map_err(|e| anyhow!("could not get plugin id: {e}"))?; if let Ok(qc_ver) = table.get::<_, String>("server_version") { debug!("checking plugin version"); let req = semver::VersionReq::parse(&qc_ver)?; if !req.matches(&semver::Version::parse(QC_VERSION).unwrap()) { return Err(anyhow!("incompatible with quectocraft version {QC_VERSION}: expected {req}")); } } else { warn!("plugin '{id}' is missing a version reqirement"); } Ok((id, table)) } pub fn load_plugins(lua: &Lua) -> mlua::Result { let plugins = lua.create_table()?; debug!("loading plugins"); let entries = match fs::read_dir("./plugins") { Ok(n) => n, Err(e) if e.kind() == ErrorKind::NotFound => { warn!("plugins directory does not exist, creating"); if let Err(e) = fs::create_dir("./plugins") { warn!("failed to create plugins directory: {e}"); } return Ok(plugins) }, Err(e) => { warn!("failed to load plugins: {e}"); return Ok(plugins) } }; for entry in entries { let entry = match entry { Ok(e) => e, Err(e) => { warn!("error reading entry: {e}"); continue } }; let path = entry.path(); let spath = path.to_str().unwrap_or(""); debug!("loading plugin at {spath}"); match load_plugin(&entry, lua) { Ok((id, table)) => { info!("loaded plugin {}", id); plugins.set(id, table)?; } Err(e) => warn!("error loading plugin at {spath}: {e}"), } } Ok(plugins) }