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:
Ryan Ward 2018-10-24 10:20:22 -04:00
parent 8eda95097e
commit bfd0821776
5 changed files with 480 additions and 1 deletions

27
-Plugin-/testPlugin1.lua Normal file
View 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
View 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
View File

@ -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
View 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
View 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()