2025-10-27 07:08:10 +11:00
from textual_window import Window
from textual . widgets import RichLog
2025-10-27 07:10:23 +11:00
from textual import work
2025-10-27 13:09:10 +11:00
from lupa import LuaRuntime , lua51
2025-10-27 07:08:10 +11:00
2025-10-27 07:13:59 +11:00
import os , json , asyncio
2025-10-27 07:08:10 +11:00
class PluginLoader ( Window ) :
def __init__ ( self ) :
super ( ) . __init__ (
id = " Plugin Loader " ,
mode = " permanent " ,
icon = " ⚙️ " ,
2025-10-27 07:13:59 +11:00
starting_horizontal = " right " ,
starting_vertical = " bottom " ,
2025-10-27 15:53:54 +11:00
start_open = True ,
allow_maximize = True
2025-10-27 07:08:10 +11:00
)
2025-10-27 07:10:23 +11:00
@work
async def find_plugins ( self ) :
2025-10-27 07:08:10 +11:00
log = self . query_one ( RichLog )
2025-10-27 13:11:33 +11:00
2025-10-27 15:53:54 +11:00
no_errors = True
2025-10-27 13:11:33 +11:00
log . write ( " [b]Setting up LUA runtime..[/] " )
self . lua_runtime = lua51 . LuaRuntime ( )
lua_runtime_stuff = {
" ui " : {
" notify " : self . notify
}
}
2025-10-27 07:08:10 +11:00
log . write ( " [b]Finding plugins...[/] " )
# Find all plugins (they're just in the plugins folder for now)
folders = [
os . path . join ( os . path . dirname ( __file__ ) , " plugins " )
]
# path to the folder of all correctly formatted plugins
plugin_paths = [ ]
for folder in folders :
log . write ( f " Searching { folder } ... " )
plugin_folders = os . listdir ( folder )
for plugin_folder in plugin_folders :
plugin_folder = os . path . join ( folder , plugin_folder )
if not os . path . isdir ( plugin_folder ) :
log . write ( f " [d]Ignoring { plugin_folder } because it is not a folder.[/] " )
2025-10-27 15:53:54 +11:00
no_errors = False
2025-10-27 07:08:10 +11:00
continue
if not os . path . isdir ( os . path . join ( plugin_folder , " lua " ) ) :
log . write ( f " [d]Ignoring { plugin_folder } because it has no \" lua \" folder.[/] " )
2025-10-27 15:53:54 +11:00
no_errors = False
2025-10-27 07:08:10 +11:00
continue
if not os . path . isfile ( os . path . join ( plugin_folder , " plugin.json " ) ) :
log . write ( f " [d]Ignoring { plugin_folder } because it has no plugin.json file.[/] " )
2025-10-27 15:53:54 +11:00
no_errors = False
2025-10-27 07:08:10 +11:00
continue
with open ( os . path . join ( plugin_folder , " plugin.json " ) , " r " ) as f :
try :
plugin_json = json . loads ( f . read ( ) )
plugin_json [ " name " ]
plugin_json [ " version " ]
plugin_json [ " author " ]
plugin_json [ " dependencies " ]
except UnicodeDecodeError :
log . write ( f " [d]Ignoring { plugin_folder } because its plugin.json file is unreadable.[/] " )
2025-10-27 15:53:54 +11:00
no_errors = False
2025-10-27 07:08:10 +11:00
continue
except json . JSONDecodeError :
log . write ( f " [d]Ignoring { plugin_folder } because its plugin.json file is malformed.[/] " )
2025-10-27 15:53:54 +11:00
no_errors = False
2025-10-27 07:08:10 +11:00
continue
except KeyError as e :
log . write ( f " [d]Ignoring { plugin_folder } because its plugin.json file is missing the field { e } . " )
2025-10-27 15:53:54 +11:00
no_errors = False
2025-10-27 07:08:10 +11:00
continue
except Exception as e :
log . write ( f " [d]Ignoring { plugin_folder } because of error: { e } .[/] " )
2025-10-27 15:53:54 +11:00
no_errors = False
2025-10-27 07:08:10 +11:00
continue
log . write ( f " [b green]FOUND[/] { plugin_json [ ' name ' ] } ( { plugin_json [ ' version ' ] } ) " )
2025-10-27 13:09:10 +11:00
for lua_file in os . listdir ( os . path . join ( plugin_folder , " lua " ) ) :
lua_file_path = os . path . join ( plugin_folder , f " lua/ { lua_file } " )
with open ( lua_file_path , " r " ) as f :
code = f . read ( )
sandbox = self . lua_runtime . eval ( " {} " )
setfenv = self . lua_runtime . eval ( " setfenv " )
sandbox . print = self . log #self.lua_runtime.globals().print
sandbox . math = self . lua_runtime . globals ( ) . math
sandbox . string = self . lua_runtime . globals ( ) . string
2025-10-27 13:11:33 +11:00
sandbox . berry = lua_runtime_stuff
2025-10-27 13:09:10 +11:00
setfenv ( 0 , sandbox )
2025-10-27 15:53:54 +11:00
try :
executed_code = self . lua_runtime . execute ( code )
except lua51 . LuaError as e :
log . write ( f " [b red]Error in { lua_file_path } : { e } [/] " )
self . notify ( " There was Lua error while loading one your installed plugins. Check the Plugin Loader window for more details. " , title = " Lua Error " , severity = " error " )
no_errors = False
continue
2025-10-27 13:09:10 +11:00
if executed_code . init :
executed_code . init ( )
if executed_code . main :
self . run_worker ( thread = True , work = executed_code . main )
2025-10-27 07:08:10 +11:00
plugin_paths . append ( plugin_folder )
2025-10-27 15:53:54 +11:00
if no_errors :
log . write ( " \n [d]Window will automatically close in 5 seconds.[/] " )
await asyncio . sleep ( 5.0 )
self . close_window ( )
2025-10-27 07:10:23 +11:00
async def on_mount ( self ) :
self . find_plugins ( )
2025-10-27 07:08:10 +11:00
def compose ( self ) :
2025-10-27 15:53:54 +11:00
yield RichLog ( markup = True , id = " plugins-log " , wrap = True )