chore: format, fix clippy warnings, bump all crates to 1.0.0
This commit is contained in:
@@ -24,11 +24,14 @@ pub fn register_provider_api(lua: &Lua, owlry: &Table) -> LuaResult<()> {
|
||||
/// Implementation of owlry.provider.register()
|
||||
fn register_provider(_lua: &Lua, config: Table) -> LuaResult<()> {
|
||||
let name: String = config.get("name")?;
|
||||
let display_name: String = config.get::<Option<String>>("display_name")?
|
||||
let display_name: String = config
|
||||
.get::<Option<String>>("display_name")?
|
||||
.unwrap_or_else(|| name.clone());
|
||||
let type_id: String = config.get::<Option<String>>("type_id")?
|
||||
let type_id: String = config
|
||||
.get::<Option<String>>("type_id")?
|
||||
.unwrap_or_else(|| name.replace('-', "_"));
|
||||
let default_icon: String = config.get::<Option<String>>("default_icon")?
|
||||
let default_icon: String = config
|
||||
.get::<Option<String>>("default_icon")?
|
||||
.unwrap_or_else(|| "application-x-addon".to_string());
|
||||
let prefix: Option<String> = config.get("prefix")?;
|
||||
|
||||
@@ -116,13 +119,14 @@ fn call_provider_function(
|
||||
// First check if there's a _providers table
|
||||
if let Ok(Value::Table(providers)) = globals.get::<Value>("_owlry_providers")
|
||||
&& let Ok(Value::Table(config)) = providers.get::<Value>(provider_name)
|
||||
&& let Ok(Value::Function(func)) = config.get::<Value>(function_name) {
|
||||
let result: Value = match query {
|
||||
Some(q) => func.call(q)?,
|
||||
None => func.call(())?,
|
||||
};
|
||||
return parse_items_result(result);
|
||||
}
|
||||
&& let Ok(Value::Function(func)) = config.get::<Value>(function_name)
|
||||
{
|
||||
let result: Value = match query {
|
||||
Some(q) => func.call(q)?,
|
||||
None => func.call(())?,
|
||||
};
|
||||
return parse_items_result(result);
|
||||
}
|
||||
|
||||
// Fall back: search through globals for functions
|
||||
// This is less reliable but handles simple cases
|
||||
@@ -153,7 +157,9 @@ fn parse_item(table: &Table) -> LuaResult<PluginItem> {
|
||||
let description: Option<String> = table.get("description")?;
|
||||
let icon: Option<String> = table.get("icon")?;
|
||||
let terminal: bool = table.get::<Option<bool>>("terminal")?.unwrap_or(false);
|
||||
let tags: Vec<String> = table.get::<Option<Vec<String>>>("tags")?.unwrap_or_default();
|
||||
let tags: Vec<String> = table
|
||||
.get::<Option<Vec<String>>>("tags")?
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut item = PluginItem::new(id, name, command);
|
||||
|
||||
@@ -176,7 +182,7 @@ fn parse_item(table: &Table) -> LuaResult<PluginItem> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::runtime::{create_lua_runtime, SandboxConfig};
|
||||
use crate::runtime::{SandboxConfig, create_lua_runtime};
|
||||
|
||||
#[test]
|
||||
fn test_register_static_provider() {
|
||||
|
||||
@@ -11,25 +11,37 @@ use std::path::{Path, PathBuf};
|
||||
pub fn register_log_api(lua: &Lua, owlry: &Table) -> LuaResult<()> {
|
||||
let log = lua.create_table()?;
|
||||
|
||||
log.set("debug", lua.create_function(|_, msg: String| {
|
||||
eprintln!("[DEBUG] {}", msg);
|
||||
Ok(())
|
||||
})?)?;
|
||||
log.set(
|
||||
"debug",
|
||||
lua.create_function(|_, msg: String| {
|
||||
eprintln!("[DEBUG] {}", msg);
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
log.set("info", lua.create_function(|_, msg: String| {
|
||||
eprintln!("[INFO] {}", msg);
|
||||
Ok(())
|
||||
})?)?;
|
||||
log.set(
|
||||
"info",
|
||||
lua.create_function(|_, msg: String| {
|
||||
eprintln!("[INFO] {}", msg);
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
log.set("warn", lua.create_function(|_, msg: String| {
|
||||
eprintln!("[WARN] {}", msg);
|
||||
Ok(())
|
||||
})?)?;
|
||||
log.set(
|
||||
"warn",
|
||||
lua.create_function(|_, msg: String| {
|
||||
eprintln!("[WARN] {}", msg);
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
log.set("error", lua.create_function(|_, msg: String| {
|
||||
eprintln!("[ERROR] {}", msg);
|
||||
Ok(())
|
||||
})?)?;
|
||||
log.set(
|
||||
"error",
|
||||
lua.create_function(|_, msg: String| {
|
||||
eprintln!("[ERROR] {}", msg);
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
owlry.set("log", log)?;
|
||||
Ok(())
|
||||
@@ -44,59 +56,79 @@ pub fn register_path_api(lua: &Lua, owlry: &Table, plugin_dir: &Path) -> LuaResu
|
||||
let path = lua.create_table()?;
|
||||
|
||||
// owlry.path.config() -> ~/.config/owlry
|
||||
path.set("config", lua.create_function(|_, ()| {
|
||||
Ok(dirs::config_dir()
|
||||
.map(|d| d.join("owlry"))
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default())
|
||||
})?)?;
|
||||
path.set(
|
||||
"config",
|
||||
lua.create_function(|_, ()| {
|
||||
Ok(dirs::config_dir()
|
||||
.map(|d| d.join("owlry"))
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.path.data() -> ~/.local/share/owlry
|
||||
path.set("data", lua.create_function(|_, ()| {
|
||||
Ok(dirs::data_dir()
|
||||
.map(|d| d.join("owlry"))
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default())
|
||||
})?)?;
|
||||
path.set(
|
||||
"data",
|
||||
lua.create_function(|_, ()| {
|
||||
Ok(dirs::data_dir()
|
||||
.map(|d| d.join("owlry"))
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.path.cache() -> ~/.cache/owlry
|
||||
path.set("cache", lua.create_function(|_, ()| {
|
||||
Ok(dirs::cache_dir()
|
||||
.map(|d| d.join("owlry"))
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default())
|
||||
})?)?;
|
||||
path.set(
|
||||
"cache",
|
||||
lua.create_function(|_, ()| {
|
||||
Ok(dirs::cache_dir()
|
||||
.map(|d| d.join("owlry"))
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.path.home() -> ~
|
||||
path.set("home", lua.create_function(|_, ()| {
|
||||
Ok(dirs::home_dir()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default())
|
||||
})?)?;
|
||||
path.set(
|
||||
"home",
|
||||
lua.create_function(|_, ()| {
|
||||
Ok(dirs::home_dir()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.path.join(...) -> joined path
|
||||
path.set("join", lua.create_function(|_, parts: mlua::Variadic<String>| {
|
||||
let mut path = PathBuf::new();
|
||||
for part in parts {
|
||||
path.push(part);
|
||||
}
|
||||
Ok(path.to_string_lossy().to_string())
|
||||
})?)?;
|
||||
path.set(
|
||||
"join",
|
||||
lua.create_function(|_, parts: mlua::Variadic<String>| {
|
||||
let mut path = PathBuf::new();
|
||||
for part in parts {
|
||||
path.push(part);
|
||||
}
|
||||
Ok(path.to_string_lossy().to_string())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.path.plugin_dir() -> plugin directory
|
||||
let plugin_dir_str = plugin_dir.to_string_lossy().to_string();
|
||||
path.set("plugin_dir", lua.create_function(move |_, ()| {
|
||||
Ok(plugin_dir_str.clone())
|
||||
})?)?;
|
||||
path.set(
|
||||
"plugin_dir",
|
||||
lua.create_function(move |_, ()| Ok(plugin_dir_str.clone()))?,
|
||||
)?;
|
||||
|
||||
// owlry.path.expand(path) -> expanded path (~ -> home)
|
||||
path.set("expand", lua.create_function(|_, path: String| {
|
||||
if path.starts_with("~/")
|
||||
&& let Some(home) = dirs::home_dir() {
|
||||
path.set(
|
||||
"expand",
|
||||
lua.create_function(|_, path: String| {
|
||||
if path.starts_with("~/")
|
||||
&& let Some(home) = dirs::home_dir()
|
||||
{
|
||||
return Ok(home.join(&path[2..]).to_string_lossy().to_string());
|
||||
}
|
||||
Ok(path)
|
||||
})?)?;
|
||||
Ok(path)
|
||||
})?,
|
||||
)?;
|
||||
|
||||
owlry.set("path", path)?;
|
||||
Ok(())
|
||||
@@ -111,76 +143,95 @@ pub fn register_fs_api(lua: &Lua, owlry: &Table, _plugin_dir: &Path) -> LuaResul
|
||||
let fs = lua.create_table()?;
|
||||
|
||||
// owlry.fs.exists(path) -> bool
|
||||
fs.set("exists", lua.create_function(|_, path: String| {
|
||||
let path = expand_path(&path);
|
||||
Ok(Path::new(&path).exists())
|
||||
})?)?;
|
||||
fs.set(
|
||||
"exists",
|
||||
lua.create_function(|_, path: String| {
|
||||
let path = expand_path(&path);
|
||||
Ok(Path::new(&path).exists())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.fs.is_dir(path) -> bool
|
||||
fs.set("is_dir", lua.create_function(|_, path: String| {
|
||||
let path = expand_path(&path);
|
||||
Ok(Path::new(&path).is_dir())
|
||||
})?)?;
|
||||
fs.set(
|
||||
"is_dir",
|
||||
lua.create_function(|_, path: String| {
|
||||
let path = expand_path(&path);
|
||||
Ok(Path::new(&path).is_dir())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.fs.read(path) -> string or nil
|
||||
fs.set("read", lua.create_function(|_, path: String| {
|
||||
let path = expand_path(&path);
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(content) => Ok(Some(content)),
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
})?)?;
|
||||
fs.set(
|
||||
"read",
|
||||
lua.create_function(|_, path: String| {
|
||||
let path = expand_path(&path);
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(content) => Ok(Some(content)),
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.fs.read_lines(path) -> table of strings or nil
|
||||
fs.set("read_lines", lua.create_function(|lua, path: String| {
|
||||
let path = expand_path(&path);
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(content) => {
|
||||
let lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
|
||||
Ok(Some(lua.create_sequence_from(lines)?))
|
||||
fs.set(
|
||||
"read_lines",
|
||||
lua.create_function(|lua, path: String| {
|
||||
let path = expand_path(&path);
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(content) => {
|
||||
let lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
|
||||
Ok(Some(lua.create_sequence_from(lines)?))
|
||||
}
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
})?)?;
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.fs.list_dir(path) -> table of filenames or nil
|
||||
fs.set("list_dir", lua.create_function(|lua, path: String| {
|
||||
let path = expand_path(&path);
|
||||
match std::fs::read_dir(&path) {
|
||||
Ok(entries) => {
|
||||
let names: Vec<String> = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.filter_map(|e| e.file_name().into_string().ok())
|
||||
.collect();
|
||||
Ok(Some(lua.create_sequence_from(names)?))
|
||||
fs.set(
|
||||
"list_dir",
|
||||
lua.create_function(|lua, path: String| {
|
||||
let path = expand_path(&path);
|
||||
match std::fs::read_dir(&path) {
|
||||
Ok(entries) => {
|
||||
let names: Vec<String> = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.filter_map(|e| e.file_name().into_string().ok())
|
||||
.collect();
|
||||
Ok(Some(lua.create_sequence_from(names)?))
|
||||
}
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
})?)?;
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.fs.read_json(path) -> table or nil
|
||||
fs.set("read_json", lua.create_function(|lua, path: String| {
|
||||
let path = expand_path(&path);
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(content) => {
|
||||
match serde_json::from_str::<serde_json::Value>(&content) {
|
||||
fs.set(
|
||||
"read_json",
|
||||
lua.create_function(|lua, path: String| {
|
||||
let path = expand_path(&path);
|
||||
match std::fs::read_to_string(&path) {
|
||||
Ok(content) => match serde_json::from_str::<serde_json::Value>(&content) {
|
||||
Ok(value) => json_to_lua(lua, &value),
|
||||
Err(_) => Ok(Value::Nil),
|
||||
}
|
||||
},
|
||||
Err(_) => Ok(Value::Nil),
|
||||
}
|
||||
Err(_) => Ok(Value::Nil),
|
||||
}
|
||||
})?)?;
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.fs.write(path, content) -> bool
|
||||
fs.set("write", lua.create_function(|_, (path, content): (String, String)| {
|
||||
let path = expand_path(&path);
|
||||
// Create parent directories if needed
|
||||
if let Some(parent) = Path::new(&path).parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
Ok(std::fs::write(&path, content).is_ok())
|
||||
})?)?;
|
||||
fs.set(
|
||||
"write",
|
||||
lua.create_function(|_, (path, content): (String, String)| {
|
||||
let path = expand_path(&path);
|
||||
// Create parent directories if needed
|
||||
if let Some(parent) = Path::new(&path).parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
Ok(std::fs::write(&path, content).is_ok())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
owlry.set("fs", fs)?;
|
||||
Ok(())
|
||||
@@ -195,18 +246,24 @@ pub fn register_json_api(lua: &Lua, owlry: &Table) -> LuaResult<()> {
|
||||
let json = lua.create_table()?;
|
||||
|
||||
// owlry.json.encode(value) -> string
|
||||
json.set("encode", lua.create_function(|lua, value: Value| {
|
||||
let json_value = lua_to_json(lua, &value)?;
|
||||
Ok(serde_json::to_string(&json_value).unwrap_or_else(|_| "null".to_string()))
|
||||
})?)?;
|
||||
json.set(
|
||||
"encode",
|
||||
lua.create_function(|lua, value: Value| {
|
||||
let json_value = lua_to_json(lua, &value)?;
|
||||
Ok(serde_json::to_string(&json_value).unwrap_or_else(|_| "null".to_string()))
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// owlry.json.decode(string) -> value or nil
|
||||
json.set("decode", lua.create_function(|lua, s: String| {
|
||||
match serde_json::from_str::<serde_json::Value>(&s) {
|
||||
Ok(value) => json_to_lua(lua, &value),
|
||||
Err(_) => Ok(Value::Nil),
|
||||
}
|
||||
})?)?;
|
||||
json.set(
|
||||
"decode",
|
||||
lua.create_function(|lua, s: String| {
|
||||
match serde_json::from_str::<serde_json::Value>(&s) {
|
||||
Ok(value) => json_to_lua(lua, &value),
|
||||
Err(_) => Ok(Value::Nil),
|
||||
}
|
||||
})?,
|
||||
)?;
|
||||
|
||||
owlry.set("json", json)?;
|
||||
Ok(())
|
||||
@@ -219,9 +276,10 @@ pub fn register_json_api(lua: &Lua, owlry: &Table) -> LuaResult<()> {
|
||||
/// Expand ~ in paths
|
||||
fn expand_path(path: &str) -> String {
|
||||
if path.starts_with("~/")
|
||||
&& let Some(home) = dirs::home_dir() {
|
||||
return home.join(&path[2..]).to_string_lossy().to_string();
|
||||
}
|
||||
&& let Some(home) = dirs::home_dir()
|
||||
{
|
||||
return home.join(&path[2..]).to_string_lossy().to_string();
|
||||
}
|
||||
path.to_string()
|
||||
}
|
||||
|
||||
@@ -305,7 +363,7 @@ fn lua_to_json(_lua: &Lua, value: &Value) -> LuaResult<serde_json::Value> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::runtime::{create_lua_runtime, SandboxConfig};
|
||||
use crate::runtime::{SandboxConfig, create_lua_runtime};
|
||||
|
||||
#[test]
|
||||
fn test_log_api() {
|
||||
@@ -316,7 +374,10 @@ mod tests {
|
||||
lua.globals().set("owlry", owlry).unwrap();
|
||||
|
||||
// Just verify it doesn't panic
|
||||
lua.load("owlry.log.info('test message')").set_name("test").call::<()>(()).unwrap();
|
||||
lua.load("owlry.log.info('test message')")
|
||||
.set_name("test")
|
||||
.call::<()>(())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -327,10 +388,18 @@ mod tests {
|
||||
register_path_api(&lua, &owlry, Path::new("/tmp/test-plugin")).unwrap();
|
||||
lua.globals().set("owlry", owlry).unwrap();
|
||||
|
||||
let home: String = lua.load("return owlry.path.home()").set_name("test").call(()).unwrap();
|
||||
let home: String = lua
|
||||
.load("return owlry.path.home()")
|
||||
.set_name("test")
|
||||
.call(())
|
||||
.unwrap();
|
||||
assert!(!home.is_empty());
|
||||
|
||||
let plugin_dir: String = lua.load("return owlry.path.plugin_dir()").set_name("test").call(()).unwrap();
|
||||
let plugin_dir: String = lua
|
||||
.load("return owlry.path.plugin_dir()")
|
||||
.set_name("test")
|
||||
.call(())
|
||||
.unwrap();
|
||||
assert_eq!(plugin_dir, "/tmp/test-plugin");
|
||||
}
|
||||
|
||||
@@ -342,10 +411,18 @@ mod tests {
|
||||
register_fs_api(&lua, &owlry, Path::new("/tmp")).unwrap();
|
||||
lua.globals().set("owlry", owlry).unwrap();
|
||||
|
||||
let exists: bool = lua.load("return owlry.fs.exists('/tmp')").set_name("test").call(()).unwrap();
|
||||
let exists: bool = lua
|
||||
.load("return owlry.fs.exists('/tmp')")
|
||||
.set_name("test")
|
||||
.call(())
|
||||
.unwrap();
|
||||
assert!(exists);
|
||||
|
||||
let is_dir: bool = lua.load("return owlry.fs.is_dir('/tmp')").set_name("test").call(()).unwrap();
|
||||
let is_dir: bool = lua
|
||||
.load("return owlry.fs.is_dir('/tmp')")
|
||||
.set_name("test")
|
||||
.call(())
|
||||
.unwrap();
|
||||
assert!(is_dir);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,11 @@ pub struct LuaRuntimeVTable {
|
||||
/// Refresh a provider's items
|
||||
pub refresh: extern "C" fn(handle: RuntimeHandle, provider_id: RStr<'_>) -> RVec<PluginItem>,
|
||||
/// Query a dynamic provider
|
||||
pub query: extern "C" fn(handle: RuntimeHandle, provider_id: RStr<'_>, query: RStr<'_>) -> RVec<PluginItem>,
|
||||
pub query: extern "C" fn(
|
||||
handle: RuntimeHandle,
|
||||
provider_id: RStr<'_>,
|
||||
query: RStr<'_>,
|
||||
) -> RVec<PluginItem>,
|
||||
/// Cleanup and drop the runtime
|
||||
pub drop: extern "C" fn(handle: RuntimeHandle),
|
||||
}
|
||||
@@ -83,11 +87,15 @@ impl RuntimeHandle {
|
||||
/// Create a null handle (reserved for error cases)
|
||||
#[allow(dead_code)]
|
||||
fn null() -> Self {
|
||||
Self { ptr: std::ptr::null_mut() }
|
||||
Self {
|
||||
ptr: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_box<T>(state: Box<T>) -> Self {
|
||||
Self { ptr: Box::into_raw(state) as *mut () }
|
||||
Self {
|
||||
ptr: Box::into_raw(state) as *mut (),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn drop_as<T>(&self) {
|
||||
@@ -147,7 +155,10 @@ impl LuaRuntimeState {
|
||||
for (id, (manifest, path)) in discovered {
|
||||
// Check version compatibility
|
||||
if !manifest.is_compatible_with(owlry_version) {
|
||||
eprintln!("owlry-lua: Plugin '{}' not compatible with owlry {}", id, owlry_version);
|
||||
eprintln!(
|
||||
"owlry-lua: Plugin '{}' not compatible with owlry {}",
|
||||
id, owlry_version
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -285,13 +296,19 @@ extern "C" fn runtime_refresh(handle: RuntimeHandle, provider_id: RStr<'_>) -> R
|
||||
state.refresh_provider(provider_id.as_str()).into()
|
||||
}
|
||||
|
||||
extern "C" fn runtime_query(handle: RuntimeHandle, provider_id: RStr<'_>, query: RStr<'_>) -> RVec<PluginItem> {
|
||||
extern "C" fn runtime_query(
|
||||
handle: RuntimeHandle,
|
||||
provider_id: RStr<'_>,
|
||||
query: RStr<'_>,
|
||||
) -> RVec<PluginItem> {
|
||||
if handle.ptr.is_null() {
|
||||
return RVec::new();
|
||||
}
|
||||
|
||||
let state = unsafe { &*(handle.ptr as *const LuaRuntimeState) };
|
||||
state.query_provider(provider_id.as_str(), query.as_str()).into()
|
||||
state
|
||||
.query_provider(provider_id.as_str(), query.as_str())
|
||||
.into()
|
||||
}
|
||||
|
||||
extern "C" fn runtime_drop(handle: RuntimeHandle) {
|
||||
|
||||
@@ -8,7 +8,7 @@ use owlry_plugin_api::PluginItem;
|
||||
|
||||
use crate::api;
|
||||
use crate::manifest::PluginManifest;
|
||||
use crate::runtime::{create_lua_runtime, load_file, SandboxConfig};
|
||||
use crate::runtime::{SandboxConfig, create_lua_runtime, load_file};
|
||||
|
||||
/// Provider registration info from Lua
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -77,11 +77,13 @@ impl LoadedPlugin {
|
||||
// Load the entry point file
|
||||
let entry_path = self.path.join(&self.manifest.plugin.entry);
|
||||
if !entry_path.exists() {
|
||||
return Err(format!("Entry point '{}' not found", self.manifest.plugin.entry));
|
||||
return Err(format!(
|
||||
"Entry point '{}' not found",
|
||||
self.manifest.plugin.entry
|
||||
));
|
||||
}
|
||||
|
||||
load_file(&lua, &entry_path)
|
||||
.map_err(|e| format!("Failed to load entry point: {}", e))?;
|
||||
load_file(&lua, &entry_path).map_err(|e| format!("Failed to load entry point: {}", e))?;
|
||||
|
||||
self.lua = Some(lua);
|
||||
Ok(())
|
||||
@@ -89,7 +91,9 @@ impl LoadedPlugin {
|
||||
|
||||
/// Get provider registrations from this plugin
|
||||
pub fn get_provider_registrations(&self) -> Result<Vec<ProviderRegistration>, String> {
|
||||
let lua = self.lua.as_ref()
|
||||
let lua = self
|
||||
.lua
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Plugin not initialized".to_string())?;
|
||||
|
||||
api::get_provider_registrations(lua)
|
||||
@@ -98,25 +102,33 @@ impl LoadedPlugin {
|
||||
|
||||
/// Call a provider's refresh function
|
||||
pub fn call_provider_refresh(&self, provider_name: &str) -> Result<Vec<PluginItem>, String> {
|
||||
let lua = self.lua.as_ref()
|
||||
let lua = self
|
||||
.lua
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Plugin not initialized".to_string())?;
|
||||
|
||||
api::call_refresh(lua, provider_name)
|
||||
.map_err(|e| format!("Refresh failed: {}", e))
|
||||
api::call_refresh(lua, provider_name).map_err(|e| format!("Refresh failed: {}", e))
|
||||
}
|
||||
|
||||
/// Call a provider's query function
|
||||
pub fn call_provider_query(&self, provider_name: &str, query: &str) -> Result<Vec<PluginItem>, String> {
|
||||
let lua = self.lua.as_ref()
|
||||
pub fn call_provider_query(
|
||||
&self,
|
||||
provider_name: &str,
|
||||
query: &str,
|
||||
) -> Result<Vec<PluginItem>, String> {
|
||||
let lua = self
|
||||
.lua
|
||||
.as_ref()
|
||||
.ok_or_else(|| "Plugin not initialized".to_string())?;
|
||||
|
||||
api::call_query(lua, provider_name, query)
|
||||
.map_err(|e| format!("Query failed: {}", e))
|
||||
api::call_query(lua, provider_name, query).map_err(|e| format!("Query failed: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
/// Discover plugins in a directory
|
||||
pub fn discover_plugins(plugins_dir: &Path) -> Result<HashMap<String, (PluginManifest, PathBuf)>, String> {
|
||||
pub fn discover_plugins(
|
||||
plugins_dir: &Path,
|
||||
) -> Result<HashMap<String, (PluginManifest, PathBuf)>, String> {
|
||||
let mut plugins = HashMap::new();
|
||||
|
||||
if !plugins_dir.exists() {
|
||||
@@ -146,13 +158,21 @@ pub fn discover_plugins(plugins_dir: &Path) -> Result<HashMap<String, (PluginMan
|
||||
Ok(manifest) => {
|
||||
let id = manifest.plugin.id.clone();
|
||||
if plugins.contains_key(&id) {
|
||||
eprintln!("owlry-lua: Duplicate plugin ID '{}', skipping {}", id, path.display());
|
||||
eprintln!(
|
||||
"owlry-lua: Duplicate plugin ID '{}', skipping {}",
|
||||
id,
|
||||
path.display()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
plugins.insert(id, (manifest, path));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("owlry-lua: Failed to load plugin at {}: {}", path.display(), e);
|
||||
eprintln!(
|
||||
"owlry-lua: Failed to load plugin at {}: {}",
|
||||
path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,10 +90,10 @@ pub struct PluginPermissions {
|
||||
impl PluginManifest {
|
||||
/// Load a plugin manifest from a plugin.toml file
|
||||
pub fn load(path: &Path) -> Result<Self, String> {
|
||||
let content = std::fs::read_to_string(path)
|
||||
.map_err(|e| format!("Failed to read manifest: {}", e))?;
|
||||
let manifest: PluginManifest = toml::from_str(&content)
|
||||
.map_err(|e| format!("Failed to parse manifest: {}", e))?;
|
||||
let content =
|
||||
std::fs::read_to_string(path).map_err(|e| format!("Failed to read manifest: {}", e))?;
|
||||
let manifest: PluginManifest =
|
||||
toml::from_str(&content).map_err(|e| format!("Failed to parse manifest: {}", e))?;
|
||||
manifest.validate()?;
|
||||
Ok(manifest)
|
||||
}
|
||||
@@ -105,7 +105,12 @@ impl PluginManifest {
|
||||
return Err("Plugin ID cannot be empty".to_string());
|
||||
}
|
||||
|
||||
if !self.plugin.id.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') {
|
||||
if !self
|
||||
.plugin
|
||||
.id
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-')
|
||||
{
|
||||
return Err("Plugin ID must be lowercase alphanumeric with hyphens".to_string());
|
||||
}
|
||||
|
||||
@@ -116,7 +121,10 @@ impl PluginManifest {
|
||||
|
||||
// Validate owlry_version constraint
|
||||
if semver::VersionReq::parse(&self.plugin.owlry_version).is_err() {
|
||||
return Err(format!("Invalid owlry_version constraint: {}", self.plugin.owlry_version));
|
||||
return Err(format!(
|
||||
"Invalid owlry_version constraint: {}",
|
||||
self.plugin.owlry_version
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -28,7 +28,7 @@ impl Default for SandboxConfig {
|
||||
allow_commands: false,
|
||||
allow_network: false,
|
||||
allow_external_fs: false,
|
||||
max_run_time_ms: 5000, // 5 seconds
|
||||
max_run_time_ms: 5000, // 5 seconds
|
||||
max_memory: 64 * 1024 * 1024, // 64 MB
|
||||
}
|
||||
}
|
||||
@@ -50,11 +50,7 @@ impl SandboxConfig {
|
||||
pub fn create_lua_runtime(_sandbox: &SandboxConfig) -> LuaResult<Lua> {
|
||||
// Create Lua with safe standard libraries only
|
||||
// We exclude: debug, io, os (dangerous parts), package (loadlib), ffi
|
||||
let libs = StdLib::COROUTINE
|
||||
| StdLib::TABLE
|
||||
| StdLib::STRING
|
||||
| StdLib::UTF8
|
||||
| StdLib::MATH;
|
||||
let libs = StdLib::COROUTINE | StdLib::TABLE | StdLib::STRING | StdLib::UTF8 | StdLib::MATH;
|
||||
|
||||
let lua = Lua::new_with(libs, mlua::LuaOptions::default())?;
|
||||
|
||||
@@ -74,11 +70,15 @@ fn setup_safe_globals(lua: &Lua) -> LuaResult<()> {
|
||||
|
||||
// Create a restricted os table with only safe functions
|
||||
let os_table = lua.create_table()?;
|
||||
os_table.set("clock", lua.create_function(|_, ()| {
|
||||
Ok(std::time::Instant::now().elapsed().as_secs_f64())
|
||||
})?)?;
|
||||
os_table.set(
|
||||
"clock",
|
||||
lua.create_function(|_, ()| Ok(std::time::Instant::now().elapsed().as_secs_f64()))?,
|
||||
)?;
|
||||
os_table.set("date", lua.create_function(os_date)?)?;
|
||||
os_table.set("difftime", lua.create_function(|_, (t2, t1): (f64, f64)| Ok(t2 - t1))?)?;
|
||||
os_table.set(
|
||||
"difftime",
|
||||
lua.create_function(|_, (t2, t1): (f64, f64)| Ok(t2 - t1))?,
|
||||
)?;
|
||||
os_table.set("time", lua.create_function(os_time)?)?;
|
||||
globals.set("os", os_table)?;
|
||||
|
||||
@@ -107,8 +107,7 @@ fn os_time(_lua: &Lua, _args: ()) -> LuaResult<i64> {
|
||||
|
||||
/// Load and run a Lua file in the given runtime
|
||||
pub fn load_file(lua: &Lua, path: &std::path::Path) -> LuaResult<()> {
|
||||
let content = std::fs::read_to_string(path)
|
||||
.map_err(mlua::Error::external)?;
|
||||
let content = std::fs::read_to_string(path).map_err(mlua::Error::external)?;
|
||||
lua.load(&content)
|
||||
.set_name(path.file_name().and_then(|n| n.to_str()).unwrap_or("chunk"))
|
||||
.into_function()?
|
||||
|
||||
Reference in New Issue
Block a user