moved the files around added demo examples and fixed a bunch of stuff!

This commit is contained in:
2025-10-01 21:07:28 +02:00
parent f1bda77ce2
commit 6580d5c4bc
10 changed files with 410 additions and 308 deletions

27
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,27 @@
{
"Lua.diagnostics.globals": [
"read_file",
"write_file",
"read_html",
"write_html",
"list_html_files",
"read_markdown",
"write_markdown",
"list_markdown_files",
"html_find_tag",
"html_replace_tag",
"html_insert_before",
"html_insert_after",
"html_wrap_content",
"md_add_header",
"md_replace_section",
"md_append_content",
"table_to_json",
"json_to_table",
"add_route",
"register_hook",
"log",
"log_warn",
"log_error"
]
}

View File

@@ -37,3 +37,21 @@ class Logger:
def log_rust_usage(message: str) -> None: def log_rust_usage(message: str) -> None:
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(f"{colorama.Fore.GREEN}[ RUST@{now} ]: {message}{colorama.Style.RESET_ALL}") print(f"{colorama.Fore.GREEN}[ RUST@{now} ]: {message}{colorama.Style.RESET_ALL}")
# <!-- LUA -->
@staticmethod
def log_lua_info(message: str) -> None:
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(f"{colorama.Fore.MAGENTA}[ LUA_INFO@{now} ]: {message}{colorama.Style.RESET_ALL}")
@staticmethod
def log_lua_error(message: str) -> None:
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(f"{colorama.Fore.MAGENTA}[ LUA_ERROR@{now} ]: {message}{colorama.Style.RESET_ALL}")
@staticmethod
def log_lua_warning(message: str) -> None:
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
print(f"{colorama.Fore.MAGENTA}[ LUA_WARNING@{now} ]: {message}{colorama.Style.RESET_ALL}")

146
lua/Actions.py Normal file
View File

@@ -0,0 +1,146 @@
from pathlib import Path
from log import Logger
import re
PLUGINS_DIR = Path(__file__).parent / "plugins"
HTML_DIR = Path(__file__).parent / "../html"
MARKDOWN_DIR = Path(__file__).parent / ".." / "markdown"
class Actions:
def _safe_read_file(self, path):
"""Safe file reading with path validation"""
try:
p = Path(path)
# Prevent directory traversal
if ".." in str(p.parts):
raise ValueError("Path traversal not allowed")
return p.read_text(encoding="utf-8")
except Exception as e:
Logger.log_lua_error(f"Error reading file {path}: {e}")
return None
@staticmethod
def _safe_write_file(self, path, content):
"""Safe file writing with path validation"""
try:
p = Path(path)
# Prevent directory traversal
if ".." in str(p.parts):
raise ValueError("Path traversal not allowed")
p.write_text(content, encoding="utf-8")
return True
except Exception as e:
Logger.log_lua_error(f"Error writing file {path}: {e}")
return False
# HTML/Markdown content operations
@staticmethod
def _read_content(self, base_dir, filename):
"""Read content from HTML or Markdown directory"""
try:
path = base_dir / filename
if not path.is_relative_to(base_dir):
raise ValueError("Invalid path")
if path.exists():
return path.read_text(encoding="utf-8")
return None
except Exception as e:
Logger.log_lua_error(f"Error reading {filename}: {e}")
return None
@staticmethod
def _write_content(self, base_dir, filename, content):
"""Write content to HTML or Markdown directory"""
try:
path = base_dir / filename
if not path.is_relative_to(base_dir):
raise ValueError("Invalid path")
path.write_text(content, encoding="utf-8")
return True
except Exception as e:
Logger.log_lua_error(f"Error writing {filename}: {e}")
return False
@staticmethod
def _list_files(self, base_dir, extension):
"""List files with given extension"""
try:
return [f.name for f in base_dir.glob(f"*{extension}")]
except Exception as e:
Logger.log_lua_error(f"Error listing files: {e}")
return []
# HTML manipulation helpers
@staticmethod
def _html_find_tag(self, html, tag):
"""Find first occurrence of HTML tag"""
pattern = f"<{tag}[^>]*>.*?</{tag}>"
match = re.search(pattern, html, re.DOTALL | re.IGNORECASE)
return match.group(0) if match else None
@staticmethod
def _html_replace_tag(self, html, tag, new_content):
"""Replace HTML tag content"""
pattern = f"(<{tag}[^>]*>).*?(</{tag}>)"
return re.sub(pattern, f"\\1{new_content}\\2", html, flags=re.DOTALL | re.IGNORECASE)
@staticmethod
def _html_insert_before(self, html, marker, content):
"""Insert content before a marker"""
return html.replace(marker, content + marker)
@staticmethod
def _html_insert_after(self, html, marker, content):
"""Insert content after a marker"""
return html.replace(marker, marker + content)
@staticmethod
def _html_wrap_content(self, html, tag, wrapper_tag, attrs=""):
"""Wrap tag content with another tag"""
pattern = f"(<{tag}[^>]*>)(.*?)(</{tag}>)"
def replacer(match):
open_tag, content, close_tag = match.groups()
return f"{open_tag}<{wrapper_tag} {attrs}>{content}</{wrapper_tag}>{close_tag}"
return re.sub(pattern, replacer, html, flags=re.DOTALL | re.IGNORECASE)
# Markdown manipulation helpers
@staticmethod
def _md_add_header(self, markdown, level, text):
"""Add header to markdown"""
prefix = "#" * level
return f"{prefix} {text}\n\n{markdown}"
@staticmethod
def _md_replace_section(self, markdown, header, new_content):
"""Replace markdown section"""
# Find section starting with header
pattern = f"(#{1,6}\\s+{re.escape(header)}.*?)(?=#{1,6}\\s+|$)"
return re.sub(pattern, f"## {header}\n\n{new_content}\n\n", markdown, flags=re.DOTALL)
@staticmethod
def _md_append_content(self, markdown, content):
"""Append content to markdown"""
return markdown.rstrip() + "\n\n" + content
# JSON conversion helpers
@staticmethod
def _table_to_json(self, lua_table):
"""Convert Lua table to JSON string"""
import json
try:
# Convert lupa table to Python dict
py_dict = dict(lua_table)
return json.dumps(py_dict)
except Exception as e:
Logger.log_lua_error(f"Error converting table to JSON: {e}")
return "{}"
@staticmethod
def _json_to_table(self, json_str):
"""Convert JSON string to Lua table"""
import json
try:
return json.loads(json_str)
except Exception as e:
Logger.log_lua_error(f"Error parsing JSON: {e}")
return {}

40
lua/PluginFSHandler.py Normal file
View File

@@ -0,0 +1,40 @@
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from log.Logger import *
logger = Logger()
class PluginFSHandler(FileSystemEventHandler):
def __init__(self, manager):
self.manager = manager
def on_modified(self, event):
try:
if event.is_directory:
return
if event.src_path.endswith(".lua"):
logger.log_lua_info(f"Plugin changed: {event.src_path}, reloading")
self.manager.reload_plugin(Path(event.src_path))
except Exception as e:
logger.log_lua_error(f"Error in on_modified: {e}")
def on_created(self, event):
try:
if event.is_directory:
return
if event.src_path.endswith(".lua"):
logger.log_lua_info(f"New plugin: {event.src_path}, loading")
self.manager.load_plugin(Path(event.src_path))
except Exception as e:
logger.error(f"Error in on_created: {e}")
def on_deleted(self, event):
try:
if event.is_directory:
return
p = Path(event.src_path)
if p.suffix == ".lua":
logger.log_lua_info(f"Plugin removed: {p.name}")
self.manager.unload_plugin(p.name)
except Exception as e:
logger.log_lua_error(f"Error in on_deleted: {e}")

106
lua/luarails.py Normal file
View File

@@ -0,0 +1,106 @@
guardrails_code = """
-- Guardrails and safe patterns for plugin development
-- Safe string operations
function safe_concat(...)
local result = {}
for i, v in ipairs({...}) do
if v ~= nil then
table.insert(result, tostring(v))
end
end
return table.concat(result)
end
-- Safe table operations
function table_contains(tbl, value)
for _, v in ipairs(tbl) do
if v == value then return true end
end
return false
end
function table_keys(tbl)
local keys = {}
for k, _ in pairs(tbl) do
table.insert(keys, k)
end
return keys
end
function table_values(tbl)
local values = {}
for _, v in pairs(tbl) do
table.insert(values, v)
end
return values
end
-- Safe string escaping
function escape_html(str)
if str == nil then return "" end
local s = tostring(str)
s = string.gsub(s, "&", "&amp;")
s = string.gsub(s, "<", "&lt;")
s = string.gsub(s, ">", "&gt;")
s = string.gsub(s, '"', "&quot;")
s = string.gsub(s, "'", "&#39;")
return s
end
-- Pattern validation
function is_valid_filename(name)
if name == nil or name == "" then return false end
-- Block directory traversal
if string.match(name, "%.%.") then return false end
if string.match(name, "/") or string.match(name, "\\\\") then return false end
return true
end
-- Safe error handling wrapper
function try_catch(fn, catch_fn)
local status, err = pcall(fn)
if not status and catch_fn then
catch_fn(err)
end
return status
end
-- Request validation
function validate_request(req, required_fields)
if type(req) ~= "table" then return false, "Request must be a table" end
for _, field in ipairs(required_fields) do
if req[field] == nil then
return false, "Missing required field: " .. field
end
end
return true, nil
end
-- Rate limiting helper (simple in-memory)
_rate_limits = _rate_limits or {}
function check_rate_limit(key, max_calls, window_seconds)
local now = os.time()
if _rate_limits[key] == nil then
_rate_limits[key] = {count = 1, window_start = now}
return true
end
local rl = _rate_limits[key]
if now - rl.window_start > window_seconds then
-- Reset window
rl.count = 1
rl.window_start = now
return true
end
if rl.count >= max_calls then
return false
end
rl.count = rl.count + 1
return true
end
log("Lua guardrails initialized")
"""

View File

@@ -6,38 +6,13 @@ from lupa import LuaRuntime, LuaError
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
from log.Logger import Logger from log.Logger import Logger
from .PluginFSHandler import PluginFSHandler
from .luarails import guardrails_code
PLUGINS_DIR = Path(__file__).parent / "plugins" PLUGINS_DIR = Path(__file__).parent / "plugins"
HTML_DIR = Path(__file__).parent / "../html" HTML_DIR = Path(__file__).parent / "../html"
MARKDOWN_DIR = Path(__file__).parent / ".." / "markdown" MARKDOWN_DIR = Path(__file__).parent / ".." / "markdown"
class PluginFSHandler(FileSystemEventHandler):
def __init__(self, manager):
self.manager = manager
def on_modified(self, event):
if event.is_directory:
return
if event.src_path.endswith(".lua"):
Logger.log_info(f"Plugin changed: {event.src_path}, reloading")
self.manager.reload_plugin(Path(event.src_path))
def on_created(self, event):
if event.is_directory:
return
if event.src_path.endswith(".lua"):
Logger.log_info(f"New plugin: {event.src_path}, loading")
self.manager.load_plugin(Path(event.src_path))
def on_deleted(self, event):
if event.is_directory:
return
p = Path(event.src_path)
if p.suffix == ".lua":
Logger.log_info(f"Plugin removed: {p.name}")
self.manager.unload_plugin(p.name)
class PluginManager: class PluginManager:
def __init__(self): def __init__(self):
self.lua = LuaRuntime(unpack_returned_tuples=True) self.lua = LuaRuntime(unpack_returned_tuples=True)
@@ -57,287 +32,65 @@ class PluginManager:
def _setup_lua_globals(self): def _setup_lua_globals(self):
"""Set up all Lua global functions and guardrails""" """Set up all Lua global functions and guardrails"""
# plugin_manager.py
from .Actions import Actions
g = self.lua.globals() g = self.lua.globals()
self.actions = Actions()
# Route and hook registration # Route and hook registration
g.add_route = self._expose_add_route g.add_route = self._expose_add_route
g.register_hook = self._expose_register_hook g.register_hook = self._expose_register_hook
# Logging - using custom Logger # Logging - using custom Logger
g.log = lambda msg: Logger.log_info(f"[lua] {msg}") # TODO: With Logger do custom Plugin Loading
g.log_warn = lambda msg: Logger.log_warning(f"[lua] {msg}") g.log = lambda msg: Logger.log_lua_info(f"PLUGIN => {msg}")
g.log_error = lambda msg: Logger.log_error(f"[lua] {msg}") g.log_warn = lambda msg: Logger.log_lua_warning(f"PLUGIN => {msg}")
g.log_lua_error = lambda msg: Logger.log_lua_error(f"PLUGIN => {msg}")
# Safe file operations (sandboxed) self.actions = Actions
g.read_file = self._safe_read_file
g.write_file = self._safe_write_file g.read_file = self.actions._safe_read_file
g.write_file = self.actions._safe_write_file
# HTML manipulation
g.read_html = lambda filename: self._read_content(HTML_DIR, filename) # HTML
g.write_html = lambda filename, content: self._write_content(HTML_DIR, filename, content) g.read_html = lambda fn: self.actions._read_content(self.actions.html_dir, fn)
g.list_html_files = lambda: self._list_files(HTML_DIR, ".html") g.write_html = lambda fn, c: self.actions._write_content(self.actions.html_dir, fn, c)
g.list_html_files = lambda: self.actions._list_files(self.actions.html_dir, ".html")
# Markdown manipulation
g.read_markdown = lambda filename: self._read_content(MARKDOWN_DIR, filename) # Markdown
g.write_markdown = lambda filename, content: self._write_content(MARKDOWN_DIR, filename, content) g.read_markdown = lambda fn: self.actions._read_content(self.actions.markdown_dir, fn)
g.list_markdown_files = lambda: self._list_files(MARKDOWN_DIR, ".md") g.write_markdown = lambda fn, c: self.actions._write_content(self.actions.markdown_dir, fn, c)
g.list_markdown_files = lambda: self.actions._list_files(self.actions.markdown_dir, ".md")
# HTML modifier helpers
g.html_find_tag = self._html_find_tag # HTML helpers
g.html_replace_tag = self._html_replace_tag g.html_find_tag = self.actions._html_find_tag
g.html_insert_before = self._html_insert_before g.html_replace_tag = self.actions._html_replace_tag
g.html_insert_after = self._html_insert_after g.html_insert_before = self.actions._html_insert_before
g.html_wrap_content = self._html_wrap_content g.html_insert_after = self.actions._html_insert_after
g.html_wrap_content = self.actions._html_wrap_content
# Markdown modifier helpers
g.md_add_header = self._md_add_header # Markdown helpers
g.md_replace_section = self._md_replace_section g.md_add_header = self.actions._md_add_header
g.md_append_content = self._md_append_content g.md_replace_section = self.actions._md_replace_section
g.md_append_content = self.actions._md_append_content
# Utility functions
g.table_to_json = self._table_to_json # JSON
g.json_to_table = self._json_to_table g.table_to_json = self.actions._table_to_json
g.json_to_table = self.actions._json_to_table
# Guardrails - predefined safe patterns # Guardrails - predefined safe patterns
self._setup_lua_guardrails() self._setup_lua_guardrails()
def _setup_lua_guardrails(self): def _setup_lua_guardrails(self):
"""Set up Lua guardrails and safe patterns"""
guardrails_code = """
-- Guardrails and safe patterns for plugin development
-- Safe string operations
function safe_concat(...)
local result = {}
for i, v in ipairs({...}) do
if v ~= nil then
table.insert(result, tostring(v))
end
end
return table.concat(result)
end
-- Safe table operations
function table_contains(tbl, value)
for _, v in ipairs(tbl) do
if v == value then return true end
end
return false
end
function table_keys(tbl)
local keys = {}
for k, _ in pairs(tbl) do
table.insert(keys, k)
end
return keys
end
function table_values(tbl)
local values = {}
for _, v in pairs(tbl) do
table.insert(values, v)
end
return values
end
-- Safe string escaping
function escape_html(str)
if str == nil then return "" end
local s = tostring(str)
s = string.gsub(s, "&", "&amp;")
s = string.gsub(s, "<", "&lt;")
s = string.gsub(s, ">", "&gt;")
s = string.gsub(s, '"', "&quot;")
s = string.gsub(s, "'", "&#39;")
return s
end
-- Pattern validation
function is_valid_filename(name)
if name == nil or name == "" then return false end
-- Block directory traversal
if string.match(name, "%.%.") then return false end
if string.match(name, "/") or string.match(name, "\\\\") then return false end
return true
end
-- Safe error handling wrapper
function try_catch(fn, catch_fn)
local status, err = pcall(fn)
if not status and catch_fn then
catch_fn(err)
end
return status
end
-- Request validation
function validate_request(req, required_fields)
if type(req) ~= "table" then return false, "Request must be a table" end
for _, field in ipairs(required_fields) do
if req[field] == nil then
return false, "Missing required field: " .. field
end
end
return true, nil
end
-- Rate limiting helper (simple in-memory)
_rate_limits = _rate_limits or {}
function check_rate_limit(key, max_calls, window_seconds)
local now = os.time()
if _rate_limits[key] == nil then
_rate_limits[key] = {count = 1, window_start = now}
return true
end
local rl = _rate_limits[key]
if now - rl.window_start > window_seconds then
-- Reset window
rl.count = 1
rl.window_start = now
return true
end
if rl.count >= max_calls then
return false
end
rl.count = rl.count + 1
return true
end
log("Lua guardrails initialized")
"""
try: try:
self.lua.execute(guardrails_code) self.lua.execute(guardrails_code)
except LuaError as e: except LuaError as e:
Logger.log_error(f"Failed to initialize Lua guardrails: {e}") Logger.log_lua_error(f"Failed to initialize Lua guardrails: {e}")
# Safe file operations
def _safe_read_file(self, path):
"""Safe file reading with path validation"""
try:
p = Path(path)
# Prevent directory traversal
if ".." in str(p.parts):
raise ValueError("Path traversal not allowed")
return p.read_text(encoding="utf-8")
except Exception as e:
Logger.log_error(f"Error reading file {path}: {e}")
return None
def _safe_write_file(self, path, content):
"""Safe file writing with path validation"""
try:
p = Path(path)
# Prevent directory traversal
if ".." in str(p.parts):
raise ValueError("Path traversal not allowed")
p.write_text(content, encoding="utf-8")
return True
except Exception as e:
Logger.log_error(f"Error writing file {path}: {e}")
return False
# HTML/Markdown content operations
def _read_content(self, base_dir, filename):
"""Read content from HTML or Markdown directory"""
try:
path = base_dir / filename
if not path.is_relative_to(base_dir):
raise ValueError("Invalid path")
if path.exists():
return path.read_text(encoding="utf-8")
return None
except Exception as e:
Logger.log_error(f"Error reading {filename}: {e}")
return None
def _write_content(self, base_dir, filename, content):
"""Write content to HTML or Markdown directory"""
try:
path = base_dir / filename
if not path.is_relative_to(base_dir):
raise ValueError("Invalid path")
path.write_text(content, encoding="utf-8")
return True
except Exception as e:
Logger.log_error(f"Error writing {filename}: {e}")
return False
def _list_files(self, base_dir, extension):
"""List files with given extension"""
try:
return [f.name for f in base_dir.glob(f"*{extension}")]
except Exception as e:
Logger.log_error(f"Error listing files: {e}")
return []
# HTML manipulation helpers
def _html_find_tag(self, html, tag):
"""Find first occurrence of HTML tag"""
pattern = f"<{tag}[^>]*>.*?</{tag}>"
match = re.search(pattern, html, re.DOTALL | re.IGNORECASE)
return match.group(0) if match else None
def _html_replace_tag(self, html, tag, new_content):
"""Replace HTML tag content"""
pattern = f"(<{tag}[^>]*>).*?(</{tag}>)"
return re.sub(pattern, f"\\1{new_content}\\2", html, flags=re.DOTALL | re.IGNORECASE)
def _html_insert_before(self, html, marker, content):
"""Insert content before a marker"""
return html.replace(marker, content + marker)
def _html_insert_after(self, html, marker, content):
"""Insert content after a marker"""
return html.replace(marker, marker + content)
def _html_wrap_content(self, html, tag, wrapper_tag, attrs=""):
"""Wrap tag content with another tag"""
pattern = f"(<{tag}[^>]*>)(.*?)(</{tag}>)"
def replacer(match):
open_tag, content, close_tag = match.groups()
return f"{open_tag}<{wrapper_tag} {attrs}>{content}</{wrapper_tag}>{close_tag}"
return re.sub(pattern, replacer, html, flags=re.DOTALL | re.IGNORECASE)
# Markdown manipulation helpers
def _md_add_header(self, markdown, level, text):
"""Add header to markdown"""
prefix = "#" * level
return f"{prefix} {text}\n\n{markdown}"
def _md_replace_section(self, markdown, header, new_content):
"""Replace markdown section"""
# Find section starting with header
pattern = f"(#{1,6}\\s+{re.escape(header)}.*?)(?=#{1,6}\\s+|$)"
return re.sub(pattern, f"## {header}\n\n{new_content}\n\n", markdown, flags=re.DOTALL)
def _md_append_content(self, markdown, content):
"""Append content to markdown"""
return markdown.rstrip() + "\n\n" + content
# JSON conversion helpers
def _table_to_json(self, lua_table):
"""Convert Lua table to JSON string"""
import json
try:
# Convert lupa table to Python dict
py_dict = dict(lua_table)
return json.dumps(py_dict)
except Exception as e:
Logger.log_error(f"Error converting table to JSON: {e}")
return "{}"
def _json_to_table(self, json_str):
"""Convert JSON string to Lua table"""
import json
try:
return json.loads(json_str)
except Exception as e:
Logger.log_error(f"Error parsing JSON: {e}")
return {}
""" Lifecycle of Plugin """ """ Lifecycle of Plugin """
def load_all(self): def load_all(self):
@@ -353,33 +106,41 @@ log("Lua guardrails initialized")
if lua_module is None: if lua_module is None:
lua_module = {} lua_module = {}
self.plugins[name] = {"path": path, "module": lua_module} self.plugins[name] = {"path": path, "module": lua_module}
Logger.log_info(f"Loaded plugin: {name}") Logger.log_lua_info(f"Loaded plugin: {name}")
except LuaError as e: except LuaError as e:
Logger.log_error(f"Lua error while loading {name}: {e}") Logger.log_lua_error(f"Lua error while loading {name}: {e}")
except Exception as e: except Exception as e:
Logger.log_error(f"Error loading plugin {name}: {e}") Logger.log_lua_error(f"Error loading plugin {name}: {e}")
finally: finally:
self._current_plugin = None self._current_plugin = None
def reload_plugin(self, path: Path): def reload_plugin(self, path: Path):
name = path.name name = path.name
# remove previous hooks/routes
self.unload_plugin(name) self.unload_plugin(name)
time.sleep(0.05) time.sleep(0.05)
self.load_plugin(path) if path.exists():
self.load_plugin(path)
else:
Logger.log_lua_warning(f"Tried to reload {name}, but file no longer exists")
def unload_plugin(self, name: str): def unload_plugin(self, name: str):
# Remove routes/hook registrations from this plugin # Remove routes/hook registrations from this plugin
to_remove_routes = [r for r, v in self.routes.items() if v[0] == name] to_remove_routes = [r for r, v in self.routes.items() if v[0] == name]
for r in to_remove_routes: for r in to_remove_routes:
del self.routes[r] del self.routes[r]
for hook, lst in list(self.hooks.items()): for hook, lst in list(self.hooks.items()):
self.hooks[hook] = [x for x in lst if x[0] != name] self.hooks[hook] = [x for x in lst if x[0] != name]
if not self.hooks[hook]: if not self.hooks[hook]:
del self.hooks[hook] del self.hooks[hook]
if name in self.plugins: if name in self.plugins:
del self.plugins[name] del self.plugins[name]
Logger.log_info(f"Unloaded plugin {name}") Logger.log_lua_info(f"Unloaded plugin {name}")
else:
Logger.log_lua_warning(f"Tried to unload {name}, but it was not loaded")
""" Expose a new API route """ """ Expose a new API route """
def _expose_add_route(self, path, lua_fn): def _expose_add_route(self, path, lua_fn):
@@ -389,13 +150,13 @@ log("Lua guardrails initialized")
if not plugin_name: if not plugin_name:
plugin_name = "<unknown>" plugin_name = "<unknown>"
self.routes[p] = (plugin_name, lua_fn) self.routes[p] = (plugin_name, lua_fn)
Logger.log_info(f"Plugin {plugin_name} registered route {p}") Logger.log_lua_info(f"Plugin {plugin_name} registered route {p}")
def _expose_register_hook(self, hook_name, lua_fn): def _expose_register_hook(self, hook_name, lua_fn):
hook = str(hook_name) hook = str(hook_name)
plugin_name = self._current_loading_plugin_name() or "<unknown>" plugin_name = self._current_loading_plugin_name() or "<unknown>"
self.hooks.setdefault(hook, []).append((plugin_name, lua_fn)) self.hooks.setdefault(hook, []).append((plugin_name, lua_fn))
Logger.log_info(f"Plugin {plugin_name} registered hook {hook}") Logger.log_lua_info(f"Plugin {plugin_name} registered hook {hook}")
def _current_loading_plugin_name(self): def _current_loading_plugin_name(self):
return getattr(self, "_current_plugin", None) return getattr(self, "_current_plugin", None)
@@ -412,7 +173,7 @@ log("Lua guardrails initialized")
return res return res
return (200, {"Content-Type": "text/html"}, str(res)) return (200, {"Content-Type": "text/html"}, str(res))
except LuaError as e: except LuaError as e:
Logger.log_error(f"Lua error in route {path}: {e}") Logger.log_lua_error(f"Lua error in route {path}: {e}")
return (500, {"Content-Type": "text/plain"}, f"Plugin error: {e}") return (500, {"Content-Type": "text/plain"}, f"Plugin error: {e}")
return None return None
@@ -427,7 +188,7 @@ log("Lua guardrails initialized")
if out is not None: if out is not None:
last = out last = out
except LuaError as e: except LuaError as e:
Logger.log_error(f"Lua error in hook {hook_name} from {plugin_name}: {e}") Logger.log_lua_error(f"Lua error in hook {hook_name} from {plugin_name}: {e}")
return last return last
""" File watcher """ """ File watcher """
@@ -436,7 +197,7 @@ log("Lua guardrails initialized")
self.fs_handler = PluginFSHandler(self) self.fs_handler = PluginFSHandler(self)
self.observer.schedule(self.fs_handler, str(PLUGINS_DIR), recursive=False) self.observer.schedule(self.fs_handler, str(PLUGINS_DIR), recursive=False)
self.observer.start() self.observer.start()
Logger.log_info("Started plugin folder watcher") Logger.log_lua_info("Started plugin folder watcher")
def stop(self): def stop(self):
self.observer.stop() self.observer.stop()

View File

@@ -0,0 +1,4 @@
add_route("/lua/test", function (req)
log("demo_route handling request for =>" .. (req.path or "unknown"))
return 200, {["Content-Type"] = "text/html"}, "<h1>Hello!</h1>"
end)

View File

@@ -1,6 +0,0 @@
-- hello.lua
add_route("/lua/hello", function(req)
log("hello.lua handling request for " .. (req.path or "unknown"))
-- return (status, headers_table, body_string)
return 200, {["Content-Type"] = "text/html"}, "<h1>Hello from Lua plugin!</h1>"
end)

6
lua/plugins/hello.lua Normal file
View File

@@ -0,0 +1,6 @@
-- hello.lua
add_route("/lua/hello", function(req)
log("hello.lua handling request for " .. (req.path or "unknown"))
-- return (status, headers_table, body_string)
return 200, {["Content-Type"] = "text/html"}, "<h1 style='text-align:center;'>Hello from Lua plugin!</h1><br /><p>Why is this here?<br/>To test the Lua plugin manager</p>"
end)

Binary file not shown.