Initial release
Well here it is, a small lua project that i got kinda serious about. More stuff in the near future
This commit is contained in:
parent
8eda95097e
commit
bfd0821776
27
-Plugin-/testPlugin1.lua
Normal file
27
-Plugin-/testPlugin1.lua
Normal file
@ -0,0 +1,27 @@
|
||||
plugin.init("PluginEpic") -- creates a folder that the plug in can use for saving data, and sets up certain data so some plug-in functions can work
|
||||
plugin.OnPreload(function()
|
||||
canRun = plugin.request("require",true)
|
||||
if not canRun then return nil,"Missing features that are required for this plugin to work!" end
|
||||
local self = plugin.expose() -- exposes this plugins namespace that is public between all plugins
|
||||
self.name = ""
|
||||
self.age = 0
|
||||
self.gender = ""
|
||||
plugin.register("newPerson",function(name,age,gender)
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.gender = gender
|
||||
end)
|
||||
plugin.register("getName",function()
|
||||
return self.name
|
||||
end)
|
||||
plugin.register("getAge",function()
|
||||
return self.age
|
||||
end)
|
||||
plugin.register("getGender",function()
|
||||
return self.gender
|
||||
end)
|
||||
end)
|
||||
|
||||
plugin.OnLoaded(function() -- called when all plug-ins have been loaded
|
||||
print(PLUGIN_NAME.." has been loaded!")
|
||||
end)
|
||||
8
-Plugin-/testPlugin2.lua
Normal file
8
-Plugin-/testPlugin2.lua
Normal file
@ -0,0 +1,8 @@
|
||||
-- plugin.init("superPlugin")
|
||||
plugin.OnLoaded(function()
|
||||
print(PLUGIN_NAME.." has been loaded!")
|
||||
local epic = plugin.getPluginRef("PluginEpic")
|
||||
epic.newPerson("Ryan",22,"male")
|
||||
print(epic.getName())
|
||||
local list = plugin.getPluginList()
|
||||
end)
|
||||
129
README.md
129
README.md
@ -1 +1,128 @@
|
||||
# pluginManager
|
||||
# pluginManager version 1.0.0
|
||||
The plugin manager was created to allow a simple way to add plugin suppoer to your lua code.
|
||||
Each plugin runs in its own enviroment and suppotes limited communication between plugins.
|
||||
|
||||
A lua rock module is in the works!
|
||||
|
||||
For now you can install by copying the pluginManager folder to where you want and set your path up.
|
||||
This library requires the multi and the bin library which can be installed using luarocks
|
||||
```
|
||||
luarocks install multi
|
||||
lucrocks install bin
|
||||
```
|
||||
|
||||
# Usage
|
||||
Main.lua
|
||||
```lua
|
||||
package.path = "./?/init.lua;"..package.path
|
||||
local plugin = require("pluginManager") -- load the plugin manager
|
||||
local multi = require("multi") -- loads the multi tasking library
|
||||
plugin.setPluginFolder("-Plugin-") -- Creates if does not exist and sets the plugin folder where plugins will be loaded
|
||||
plugin.setProtection(true) -- Defaults to true, if true all plugins are loaded using pcall
|
||||
plugin.expose("std") -- string or table, std allows all non dangerious features to work. "*" imports all and a table can also be used to select certain items. expose can be used more that once. The second argument allows for the golbal table to be used in read only mode
|
||||
plugin.expose({ -- expose custom modules and other features to the global namespace of each plugin
|
||||
multi = multi,
|
||||
})
|
||||
-- "*" allows you to use _G as the enviroment
|
||||
-- setting readonly, the second argument to true makes plugins able to read from global, but not write to it.
|
||||
-- you can use a table instead of a string and put the name spaces directly that you want
|
||||
--[[
|
||||
plugin.expose({
|
||||
io = {io.tmpfile,io.write},
|
||||
os = {os.clock,os.date,os.difftime,os.exit,os.getenv,os.remove,os.rename,os.setlocale,os.time,os.tmpname},
|
||||
},readonly) -- if readonly is true, each plugin is exposed a reference to the main _G table, but in readonly form. They will not be able to modify it!
|
||||
]]
|
||||
plugin.grant("testPlugin1.lua",{ -- expost certain modules or features to a specific plugin
|
||||
require = require
|
||||
})
|
||||
plugin.load() -- loads plugins
|
||||
print("Done loading...")
|
||||
multi:newTLoop(function()
|
||||
--~ plugin.reloadPlugins() -- this is in the works, but right now I am having som issues with completely clean reloading of plugins. Should be done by the next update
|
||||
end,1)
|
||||
multi:mainloop()
|
||||
```
|
||||
|
||||
testPlugin1.lua
|
||||
```lua
|
||||
plugin.init("PluginEpic") -- creates a folder that the plug in can use for saving data, and sets up certain data so some plug-in functions can work
|
||||
plugin.OnPreload(function()
|
||||
canRun = plugin.request("require",true)
|
||||
if not canRun then return nil,"Missing features that are required for this plugin to work!" end
|
||||
local self = plugin.expose() -- exposes this plugins namespace that is public between all plugins
|
||||
-- below we will create an object that the next plugin will be able to use!
|
||||
self.name = ""
|
||||
self.age = 0
|
||||
self.gender = ""
|
||||
plugin.register("newPerson",function(name,age,gender) -- you can also create them directly on the 'self' variable that was exposed as well. I just like the module.method format.
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.gender = gender
|
||||
end)
|
||||
plugin.register("getName",function()
|
||||
return self.name
|
||||
end)
|
||||
plugin.register("getAge",function()
|
||||
return self.age
|
||||
end)
|
||||
plugin.register("getGender",function()
|
||||
return self.gender
|
||||
end)
|
||||
end)
|
||||
|
||||
plugin.OnLoaded(function() -- called when all plug-ins have been loaded
|
||||
print(PLUGIN_NAME.." has been loaded!") -- This connection is also the connection that you would use when trying to interact with other plugin's features
|
||||
end)
|
||||
```
|
||||
|
||||
testPlugin2.lua
|
||||
```lua
|
||||
-- plugin.init("superPlugin") -- if you do not init a plugin it gets auto inti with the filename as the plugins name
|
||||
plugin.OnLoaded(function()
|
||||
print(PLUGIN_NAME.." has been loaded!")
|
||||
local epic = plugin.getPluginRef("PluginEpic") -- get the plugin's PluginEpic object that we created
|
||||
epic.newPerson("Ryan",22,"male")
|
||||
print(epic.getName()) -- and there we go it works!
|
||||
local list = plugin.getPluginList() -- you can also grab a list of plugins using this
|
||||
end)
|
||||
```
|
||||
# TODO
|
||||
- Add plugin reloading
|
||||
- Add luarock install
|
||||
- Add plugin signing so you can have trusted plugins
|
||||
- Allow plugin.grant to use plugin names as well
|
||||
- Add a special folder in which plugins can require from.
|
||||
- Think of new features to add
|
||||
|
||||
|
||||
# Reference
|
||||
**Note:** There are two versions of the "plugin" namespace. One that is exposed to the main code that loads the plugins and another that is exposed to each loaded plugin.
|
||||
|
||||
Main plugin.*****
|
||||
**plugin.version** -- The version of the library
|
||||
**plugin.setPluginFolder(path)** -- Sets the path that plugins should be located
|
||||
**plugin.setProtection(set)** -- Defaults to true, if true all plugins are loaded in pcall
|
||||
**plugin.expose(table or string)** -- Exposes modules or features that plugins are allowed to use
|
||||
**plugin.load()** -- loads plugins
|
||||
**plugin.grant(path, table)** -- exposes features to a certain plugin
|
||||
|
||||
Plugins plugin.*****
|
||||
**plugin.version** -- The version of the library
|
||||
**plugin.OnLoaded(func)** -- A connection that can be bound to inside of a plugin that is triggered when all plugins have been loaded
|
||||
**plugin.OnPreload(func)** -- A connection that can be bound to inside of a plugin that is triggered when each plugin is loaded
|
||||
**plugin.OnReboot(func)** -- Not yet implemented, but when it is, this will allos plugins to be able to clean up garbage that they create and handle being reloaded
|
||||
**plugin.init(name,version)** -- Sets the plugins name and an option to set its version when exposing an object to other plugins to use
|
||||
**plugin.requeat(feature/module,cry)** -- returns true if the plugin has access to these features in it global space, false otherwise. If cry is set to true then a message of what is lacking is printed on the console
|
||||
**plugin.openFreshFile(path)** -- Opens a fresh streamed file. This uses the bin library so refer to it for working with these. All files are written to in seperate folders that are the same as the plugins name.
|
||||
**plugin.openFile(path)** -- Opens a file in stream mode, where data is kept from the last session
|
||||
**plugin.deleteFile(path)** -- Delets a file
|
||||
**plugin.setGlobal(name,value)** -- sets a global value that all plugins can see as long as they use getGlobal()
|
||||
**plugin.getGlobal(name)** -- returns a global value
|
||||
**plugin.getPluginList()** -- returns a list of all loaded plugins
|
||||
**plugin.getPluginRef(name)** -- returns a readnly reference to a plugins exposed namespace
|
||||
**plugin.register(name,value)** -- registers an element to the plugins exposed namespace
|
||||
**plugin.expose()** -- returns a reference to the current plugins exposed namespace, what other plugins can see
|
||||
**plugin.getName()** -- returns the name of the current plugin
|
||||
|
||||
# Known issues
|
||||
These issues are all security issues. I am workig on them and by next version all should be good
|
||||
290
pluginManager/init.lua
Normal file
290
pluginManager/init.lua
Normal file
@ -0,0 +1,290 @@
|
||||
local version = "1.0.0"
|
||||
local bin = require("bin")
|
||||
local multi = require("multi")
|
||||
local function merge(t1, t2)
|
||||
for k,v in pairs(t2) do
|
||||
if type(v) == 'table' then
|
||||
if type(t1[k] or false) == 'table' then
|
||||
merge(t1[k] or {}, t2[k] or {})
|
||||
else
|
||||
t1[k] = v
|
||||
end
|
||||
else
|
||||
t1[k] = v
|
||||
end
|
||||
end
|
||||
return t1
|
||||
end
|
||||
local function getOS()
|
||||
if package.config:sub(1,1)=='\\' then
|
||||
return 'windows'
|
||||
else
|
||||
return 'unix'
|
||||
end
|
||||
end
|
||||
local function mkDir(dirname)
|
||||
os.execute('mkdir "' .. dirname..'"')
|
||||
end
|
||||
local function capture(cmd, raw)
|
||||
local f = assert(io.popen(cmd, 'r'))
|
||||
local s = assert(f:read('*a'))
|
||||
f:close()
|
||||
if raw then return s end
|
||||
s = string.gsub(s, '^%s+', '')
|
||||
s = string.gsub(s, '%s+$', '')
|
||||
s = string.gsub(s, '[\n\r]+', ' ')
|
||||
return s
|
||||
end
|
||||
local function getWorkingDir()
|
||||
return io.popen'cd':read'*l'
|
||||
end
|
||||
function getDir(dir)
|
||||
if not dir then return getWorkingDir() end
|
||||
if getOS()=='unix' then
|
||||
return capture('cd '..dir..' ; cd')
|
||||
else
|
||||
return capture('cd '..dir..' & cd')
|
||||
end
|
||||
end
|
||||
function dirExists(strFolderName)
|
||||
strFolderName = strFolderName or getDir()
|
||||
local fileHandle, strError = io.open(strFolderName..'\\*.*','r')
|
||||
if fileHandle ~= nil then
|
||||
io.close(fileHandle)
|
||||
return true
|
||||
else
|
||||
if string.match(strError,'No such file or directory') then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
function scandir(directory)
|
||||
if not dirExists(directory) then error("Must enter a valid location for scanning a directory!") end
|
||||
local i, t, popen = 0, {}, io.popen
|
||||
if getOS()=='unix' then
|
||||
for filename in popen('ls -a "'..directory..'"'):lines() do
|
||||
i = i + 1
|
||||
t[i] = filename
|
||||
end
|
||||
else
|
||||
for filename in popen('dir "'..directory..'" /b'):lines() do
|
||||
i = i + 1
|
||||
t[i] = filename
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
local function split(str)
|
||||
local tab = {}
|
||||
for word in string.gmatch(str, '([^,]+)') do
|
||||
table.insert(tab,word)
|
||||
end
|
||||
return tab
|
||||
end
|
||||
local pluginLocation
|
||||
local protection = true
|
||||
local exposed = {}
|
||||
local function _setPluginFolder(path)
|
||||
if not dirExists(path) then
|
||||
mkDir(path)
|
||||
end
|
||||
pluginLocation = path
|
||||
end
|
||||
local function _setProtection(bool)
|
||||
protection = bool
|
||||
end
|
||||
local function _expose(tab,readonly)
|
||||
if type(tab)=="string" then
|
||||
if tab=="*" or tab:match("*") then
|
||||
merge(exposed,_G)
|
||||
return
|
||||
elseif tab=="std" then
|
||||
tab = [[_VERSION,assert,collectgarbage,error,getfenv,getmetatable,ipairs,loadstring,module,next,pairs,pcall,print,rawequal,rawget,rawset,select,setfenv,setmetatable,tonumber,tostring,type,unpack,xpcall,math,coroutine,string,table]]
|
||||
_expose({
|
||||
io = {io.tmpfile,io.write},
|
||||
os = {os.clock,os.date,os.difftime,os.exit,os.getenv,os.remove,os.rename,os.setlocale,os.time,os.tmpname},
|
||||
_G = exposed
|
||||
},readonly)
|
||||
end
|
||||
local tab = split(tab)
|
||||
for i = 1,#tab do
|
||||
exposed[tab[i]] = _G[tab[i]]
|
||||
end
|
||||
elseif type(tab)=="table" then
|
||||
merge(exposed,tab)
|
||||
if readonly then
|
||||
setmetatable(exposed,{
|
||||
__index = _G
|
||||
})
|
||||
end
|
||||
else
|
||||
if readonly then
|
||||
setmetatable(exposed,{
|
||||
__index = _G
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
local function file_exists(name)
|
||||
local f=io.open(name,"r")
|
||||
if f~=nil then io.close(f) return true else return false end
|
||||
end
|
||||
local GLOBAL = {__PluginList={},vars = {}}
|
||||
local conn = multi:newConnection(true)
|
||||
local conn2 = multi:newConnection(true)
|
||||
local conn3 = multi:newConnection(true)
|
||||
local conn4 = multi:newConnection(true) -- Cleanup
|
||||
local conn5 = multi:newConnection(true) -- Cleanup
|
||||
-- And yes these names are really bad, doesn't matter
|
||||
local plugs = {}
|
||||
local function cleanTab(tab)
|
||||
for i,v in pairs(tab) do
|
||||
tab[i]=nil
|
||||
end
|
||||
end
|
||||
local function load_(path)
|
||||
if not file_exists(pluginLocation..package.config:sub(1,1)..path) then return end
|
||||
local chunk = loadfile(pluginLocation..package.config:sub(1,1)..path)
|
||||
if not chunk then error("Could not load plugin: "..path) end
|
||||
local temp = {}
|
||||
merge(temp,exposed)
|
||||
merge(temp,{
|
||||
plugin = {
|
||||
version = version,
|
||||
OnLoaded = conn,
|
||||
OnPreload = conn3,
|
||||
OnReboot = conn4,
|
||||
init = function(name,version)
|
||||
temp["PLUGIN_NAME"]=name
|
||||
GLOBAL[name] = {version = version}
|
||||
table.insert(GLOBAL.__PluginList,name)
|
||||
if not dirExists(pluginLocation..package.config:sub(1,1)..name) then
|
||||
mkDir(pluginLocation..package.config:sub(1,1)..name)
|
||||
end
|
||||
end,
|
||||
request = function(method,err)
|
||||
local met
|
||||
if type(method) == "string" then
|
||||
met = split(method)
|
||||
elseif type(method) == "table" then
|
||||
met = method
|
||||
end
|
||||
local count = 0
|
||||
local max = #met
|
||||
local missing = {}
|
||||
for ii = 1,#met do
|
||||
if temp[met[ii]] then
|
||||
count = count + 1
|
||||
else
|
||||
table.insert(missing,met[ii])
|
||||
end
|
||||
end
|
||||
if err and count ~= max then
|
||||
print((temp["PLUGIN_NAME"] or "Unnamed Plugin").." is missing some resources to be able to function properly! Try granting these features to the plugin if you trust it: ")
|
||||
for i = 1,#missing do
|
||||
print("> "..missing[i])
|
||||
end
|
||||
elseif count ~= max then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end,
|
||||
openFreshFile = function(path)
|
||||
local t = package.config:sub(1,1)
|
||||
return bin.freshStream(pluginLocation..t..temp["PLUGIN_NAME"]..t..path)
|
||||
end,
|
||||
openFile = function(path)
|
||||
local t = package.config:sub(1,1)
|
||||
return bin.stream(pluginLocation..t..temp["PLUGIN_NAME"]..t..path,false)
|
||||
end,
|
||||
deleteFile = function(path)
|
||||
local t = package.config:sub(1,1)
|
||||
os.remove(pluginLocation..t..temp["PLUGIN_NAME"]..t..path,false)
|
||||
end,
|
||||
setGlobal = function(var,val)
|
||||
GLOBAL.vars[var]=val
|
||||
end,
|
||||
getGlobal = function(var)
|
||||
return GLOBAL.vars[var]
|
||||
end,
|
||||
getPluginList = function()
|
||||
return GLOBAL.__PluginList
|
||||
end,
|
||||
getPluginRef = function(name)
|
||||
local meh = {}
|
||||
local link = GLOBAL[name]
|
||||
setmetatable(meh,{
|
||||
__index = link -- Cannot alter a plugin's created domain but can read from it
|
||||
})
|
||||
return meh
|
||||
end,
|
||||
register = function(name,value)
|
||||
GLOBAL[temp["PLUGIN_NAME"]][name] = value
|
||||
end,
|
||||
expose = function()
|
||||
return GLOBAL[temp["PLUGIN_NAME"]]
|
||||
end,
|
||||
getName = function()
|
||||
return temp["PLUGIN_NAME"]
|
||||
end,
|
||||
}
|
||||
})
|
||||
conn2:Fire(temp,path)
|
||||
setfenv(chunk, temp)
|
||||
chunk()
|
||||
table.insert(plugs,{chunk,temp,GLOBAL,path})
|
||||
conn5(function()
|
||||
cleanTab(GLOBAL)
|
||||
GLOBAL.__PluginList={}
|
||||
GLOBAL.vars = {}
|
||||
end)
|
||||
if not temp["PLUGIN_NAME"] then
|
||||
temp.plugin.init(path:sub(1,-5))
|
||||
end
|
||||
end
|
||||
local granted = {}
|
||||
conn2(function(tab,path)
|
||||
if granted[path] then
|
||||
merge(tab,granted[path])
|
||||
end
|
||||
end)
|
||||
local function _grant(path,tab)
|
||||
granted[path] = tab
|
||||
end
|
||||
local function _load()
|
||||
local files = scandir(pluginLocation)
|
||||
for i = 1,#files do
|
||||
if protection then
|
||||
a,b = pcall(load_,files[i])
|
||||
if not a then print(b) end
|
||||
else
|
||||
load_(files[i])
|
||||
end
|
||||
end
|
||||
conn3:Fire()
|
||||
conn:Fire(#files)
|
||||
end
|
||||
local function _reload()
|
||||
conn:Bind({}) -- bind to a new connection space
|
||||
conn3:Bind({}) -- this in turn removes all references that are connected to the connection obj
|
||||
conn4:Fire() -- allow the plugin to clean up some stuff
|
||||
conn5:Fire() -- resets all the GLOBAL namespace that is exposed to all plugins
|
||||
collectgarbage()
|
||||
for i=1,#plugs do
|
||||
plugs[i][1]() -- reload the plugins
|
||||
end
|
||||
conn3:Fire()
|
||||
end
|
||||
local plugin = {}
|
||||
plugin.version = version
|
||||
plugin.setPluginFolder = _setPluginFolder
|
||||
plugin.setProtection =_setProtection
|
||||
plugin.expose =_expose
|
||||
plugin.load =_load
|
||||
plugin.grant = _grant
|
||||
-- plugin.reloadPlugins = _reload
|
||||
return plugin
|
||||
27
test.lua
Normal file
27
test.lua
Normal file
@ -0,0 +1,27 @@
|
||||
package.path = "./?/init.lua;"..package.path
|
||||
local plugin = require("pluginManager")
|
||||
local multi = require("multi")
|
||||
plugin.setPluginFolder("-Plugin-") -- Creates if does not exist and sets the plugin folder where plugins will be loaded
|
||||
plugin.setProtection(true) -- Defaults to true
|
||||
plugin.expose("std") -- string or table, std allows all non dangerious features to work. "*" imports all and a table can also be used to select certain items. expose can be used more that once. The second argument allows for the golbal table to be used in read only mode
|
||||
plugin.expose({
|
||||
multi = multi,
|
||||
})
|
||||
plugin.grant("testPlugin1.lua",{
|
||||
require = require
|
||||
})
|
||||
-- "*" allows you to use _G as the enviroment
|
||||
-- setting readonly, the second argument to true makes plugins able to read from global, but not write to it.
|
||||
-- you can use a table instead of a string and put the name spaces directly that you want
|
||||
--[[
|
||||
plugin.expose({
|
||||
io = {io.tmpfile,io.write},
|
||||
os = {os.clock,os.date,os.difftime,os.exit,os.getenv,os.remove,os.rename,os.setlocale,os.time,os.tmpname},
|
||||
},readonly)
|
||||
]]
|
||||
plugin.load() -- loads plugins
|
||||
print("Done loading...")
|
||||
multi:newTLoop(function()
|
||||
--~ plugin.reloadPlugins()
|
||||
end,1)
|
||||
multi:mainloop()
|
||||
Loading…
x
Reference in New Issue
Block a user