🔧 build: 调整oop相关库
parent
2f2409bad9
commit
7540100b4b
@ -1,33 +0,0 @@
|
|||||||
-- https://github.com/Tjakka5/Enum
|
|
||||||
|
|
||||||
local Enum = {}
|
|
||||||
local Meta = {
|
|
||||||
__index = function(_, k)
|
|
||||||
error("Attempt to index non-existant enum '" .. tostring(k) .. "'.", 2)
|
|
||||||
end,
|
|
||||||
__newindex = function()
|
|
||||||
error("Attempt to write to static enum", 2)
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
function Enum.new(...)
|
|
||||||
local values = {...}
|
|
||||||
|
|
||||||
if type(values[1]) == "table" then
|
|
||||||
values = values[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
local enum = {}
|
|
||||||
|
|
||||||
for i = 1, #values do
|
|
||||||
enum[values[i]] = values[i]
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable(enum, Meta)
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable(Enum, {
|
|
||||||
__call = function(_, ...)
|
|
||||||
return Enum.new(...)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
@ -1,199 +0,0 @@
|
|||||||
-- https://github.com/jojo59516/middleclass
|
|
||||||
local middleclass = {}
|
|
||||||
|
|
||||||
local function _createIndexWrapper(aClass, f)
|
|
||||||
if f == nil then
|
|
||||||
return aClass.__instanceDict
|
|
||||||
elseif type(f) == "function" then
|
|
||||||
return function(self, name)
|
|
||||||
local value = aClass.__instanceDict[name]
|
|
||||||
|
|
||||||
if value ~= nil then
|
|
||||||
return value
|
|
||||||
else
|
|
||||||
return (f(self, name))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else -- if type(f) == "table" then
|
|
||||||
return function(self, name)
|
|
||||||
local value = aClass.__instanceDict[name]
|
|
||||||
|
|
||||||
if value ~= nil then
|
|
||||||
return value
|
|
||||||
else
|
|
||||||
return f[name]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function _propagateInstanceMethod(aClass, name, f)
|
|
||||||
f = name == "__index" and _createIndexWrapper(aClass, f) or f
|
|
||||||
aClass.__instanceDict[name] = f
|
|
||||||
|
|
||||||
for subclass in pairs(aClass.subclasses) do
|
|
||||||
if rawget(subclass.__declaredMethods, name) == nil then
|
|
||||||
_propagateInstanceMethod(subclass, name, f)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function _declareInstanceMethod(aClass, name, f)
|
|
||||||
aClass.__declaredMethods[name] = f
|
|
||||||
|
|
||||||
if f == nil and aClass.super then
|
|
||||||
f = aClass.super.__instanceDict[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
_propagateInstanceMethod(aClass, name, f)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function _tostring(self)
|
|
||||||
return "class " .. self.name
|
|
||||||
end
|
|
||||||
local function _call(self, ...)
|
|
||||||
return self:new(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function _createClass(name, super)
|
|
||||||
local dict = {}
|
|
||||||
dict.__index = dict
|
|
||||||
|
|
||||||
local aClass = {
|
|
||||||
name = name,
|
|
||||||
super = super,
|
|
||||||
static = {},
|
|
||||||
__instanceDict = dict,
|
|
||||||
__declaredMethods = {},
|
|
||||||
subclasses = setmetatable({}, {
|
|
||||||
__mode = 'k',
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
if super then
|
|
||||||
setmetatable(aClass.static, {
|
|
||||||
__index = function(_, k)
|
|
||||||
local result = rawget(dict, k)
|
|
||||||
if result == nil then
|
|
||||||
return super.static[k]
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
else
|
|
||||||
setmetatable(aClass.static, {
|
|
||||||
__index = function(_, k)
|
|
||||||
return rawget(dict, k)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
setmetatable(aClass, {
|
|
||||||
__index = aClass.static,
|
|
||||||
__tostring = _tostring,
|
|
||||||
__call = _call,
|
|
||||||
__newindex = _declareInstanceMethod,
|
|
||||||
})
|
|
||||||
|
|
||||||
return aClass
|
|
||||||
end
|
|
||||||
|
|
||||||
local function _includeMixin(aClass, mixin)
|
|
||||||
assert(type(mixin) == 'table', "mixin must be a table")
|
|
||||||
|
|
||||||
for name, method in pairs(mixin) do
|
|
||||||
if name ~= "included" and name ~= "static" then
|
|
||||||
aClass[name] = method
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for name, method in pairs(mixin.static or {}) do
|
|
||||||
aClass.static[name] = method
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(mixin.included) == "function" then
|
|
||||||
mixin:included(aClass)
|
|
||||||
end
|
|
||||||
return aClass
|
|
||||||
end
|
|
||||||
|
|
||||||
local DefaultMixin = {
|
|
||||||
__tostring = function(self)
|
|
||||||
return "instance of " .. tostring(self.class)
|
|
||||||
end,
|
|
||||||
|
|
||||||
initialize = function(self, ...)
|
|
||||||
end,
|
|
||||||
|
|
||||||
isInstanceOf = function(self, aClass)
|
|
||||||
return type(aClass) == 'table' and type(self) == 'table' and
|
|
||||||
(self.class == aClass or type(self.class) == 'table' and type(self.class.isSubclassOf) == 'function' and
|
|
||||||
self.class:isSubclassOf(aClass))
|
|
||||||
end,
|
|
||||||
|
|
||||||
static = {
|
|
||||||
allocate = function(self)
|
|
||||||
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
|
|
||||||
return setmetatable({
|
|
||||||
class = self,
|
|
||||||
}, self.__instanceDict)
|
|
||||||
end,
|
|
||||||
|
|
||||||
new = function(self, ...)
|
|
||||||
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
|
|
||||||
local instance = self:allocate()
|
|
||||||
instance:initialize(...)
|
|
||||||
return instance
|
|
||||||
end,
|
|
||||||
|
|
||||||
subclass = function(self, name)
|
|
||||||
assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
|
|
||||||
assert(type(name) == "string", "You must provide a name(string) for your class")
|
|
||||||
|
|
||||||
local subclass = _createClass(name, self)
|
|
||||||
|
|
||||||
for methodName, f in pairs(self.__instanceDict) do
|
|
||||||
if not (methodName == "__index" and type(f) == "table") then
|
|
||||||
_propagateInstanceMethod(subclass, methodName, f)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
subclass.initialize = function(instance, ...)
|
|
||||||
return self.initialize(instance, ...)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.subclasses[subclass] = true
|
|
||||||
self:subclassed(subclass)
|
|
||||||
|
|
||||||
return subclass
|
|
||||||
end,
|
|
||||||
|
|
||||||
subclassed = function(self, other)
|
|
||||||
end,
|
|
||||||
|
|
||||||
isSubclassOf = function(self, other)
|
|
||||||
return type(other) == 'table' and type(self.super) == 'table' and
|
|
||||||
(self.super == other or self.super:isSubclassOf(other))
|
|
||||||
end,
|
|
||||||
|
|
||||||
include = function(self, ...)
|
|
||||||
assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
|
|
||||||
for _, mixin in ipairs({...}) do
|
|
||||||
_includeMixin(self, mixin)
|
|
||||||
end
|
|
||||||
return self
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function middleclass.class(name, super)
|
|
||||||
assert(type(name) == 'string', "A name (string) is needed for the new class")
|
|
||||||
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
|
|
||||||
end
|
|
||||||
|
|
||||||
setmetatable(middleclass, {
|
|
||||||
__call = function(_, ...)
|
|
||||||
return middleclass.class(...)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
return middleclass
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
-- https://github.com/ichesnokov/middleclass-mixin-singleton
|
|
||||||
|
|
||||||
local singleton = {
|
|
||||||
static = {},
|
|
||||||
}
|
|
||||||
|
|
||||||
function singleton:included(class)
|
|
||||||
-- Override new to throw an error, but store a reference to the old "new" method
|
|
||||||
class.static._new = class.static.new
|
|
||||||
class.static.new = function()
|
|
||||||
error("Use " .. class.name .. ":instance() instead of :new()")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function singleton.static:instance(...)
|
|
||||||
self._instance = self._instance or self._new(self, ...) -- use old "new" method
|
|
||||||
return self._instance
|
|
||||||
end
|
|
||||||
|
|
||||||
function singleton.static:clear_instance()
|
|
||||||
self._instance = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return singleton
|
|
||||||
@ -0,0 +1,294 @@
|
|||||||
|
local skynet = require "skynet"
|
||||||
|
|
||||||
|
local type = type
|
||||||
|
local pcall = pcall
|
||||||
|
local pairs = pairs
|
||||||
|
local ipairs = ipairs
|
||||||
|
local rawget = rawget
|
||||||
|
local rawset = rawset
|
||||||
|
local tostring = tostring
|
||||||
|
local ssub = string.sub
|
||||||
|
local sformat = string.format
|
||||||
|
local dgetinfo = debug.getinfo
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local tinsert = table.insert
|
||||||
|
|
||||||
|
local is_class
|
||||||
|
|
||||||
|
-- 类模板
|
||||||
|
local class_tpls = {}
|
||||||
|
|
||||||
|
local function deep_copy(src, dst)
|
||||||
|
local ndst = dst or {}
|
||||||
|
for key, value in pairs(src or {}) do
|
||||||
|
if is_class(value) then
|
||||||
|
ndst[key] = value()
|
||||||
|
elseif (type(value) == "table") then
|
||||||
|
ndst[key] = deep_copy(value)
|
||||||
|
else
|
||||||
|
ndst[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ndst
|
||||||
|
end
|
||||||
|
|
||||||
|
local function mixin_init(class, object, ...)
|
||||||
|
if class.__super then
|
||||||
|
mixin_init(class.__super, object, ...)
|
||||||
|
end
|
||||||
|
for _, mixin in ipairs(class.__mixins) do
|
||||||
|
if type(mixin.__init) == "function" then
|
||||||
|
mixin.__init(object, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return object
|
||||||
|
end
|
||||||
|
|
||||||
|
local function object_init(class, object, ...)
|
||||||
|
if class.__super then
|
||||||
|
object_init(class.__super, object, ...)
|
||||||
|
end
|
||||||
|
if type(class.__init) == "function" then
|
||||||
|
class.__init(object, ...)
|
||||||
|
end
|
||||||
|
return object
|
||||||
|
end
|
||||||
|
|
||||||
|
local function object_release(class, object, ...)
|
||||||
|
if type(class.__release) == "function" then
|
||||||
|
class.__release(object, ...)
|
||||||
|
end
|
||||||
|
if class.__super then
|
||||||
|
object_release(class.__super, object, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function object_defer(class, object, ...)
|
||||||
|
if type(class.__defer) == "function" then
|
||||||
|
class.__defer(object, ...)
|
||||||
|
end
|
||||||
|
if class.__super then
|
||||||
|
object_defer(class.__super, object, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function object_default(class, object)
|
||||||
|
if class.__super then
|
||||||
|
object_default(class.__super, object)
|
||||||
|
end
|
||||||
|
local defaults = deep_copy(class.__default)
|
||||||
|
for name, param in pairs(defaults) do
|
||||||
|
object[name] = param[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function object_tostring(object)
|
||||||
|
if type(object.tostring) == "function" then
|
||||||
|
return object:tostring()
|
||||||
|
end
|
||||||
|
return sformat("class:%s(%s)", object.__moudle, object.__addr)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function object_constructor(class, ...)
|
||||||
|
local obj = {}
|
||||||
|
object_default(class, obj)
|
||||||
|
obj.__addr = ssub(tostring(obj), 7)
|
||||||
|
local object = setmetatable(obj, class.__vtbl)
|
||||||
|
object_init(class, object, ...)
|
||||||
|
mixin_init(class, object, ...)
|
||||||
|
return object
|
||||||
|
end
|
||||||
|
|
||||||
|
local function new(class, ...)
|
||||||
|
if class.__singleton then
|
||||||
|
local inst_obj = rawget(class, "__inst")
|
||||||
|
if not inst_obj then
|
||||||
|
inst_obj = object_constructor(class, ...)
|
||||||
|
-- 定义单例方法
|
||||||
|
local inst_func = function()
|
||||||
|
return inst_obj
|
||||||
|
end
|
||||||
|
rawset(class, "__inst", inst_obj)
|
||||||
|
rawset(class, "inst", inst_func)
|
||||||
|
end
|
||||||
|
return inst_obj
|
||||||
|
else
|
||||||
|
return object_constructor(class, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function index(class, field)
|
||||||
|
return class.__vtbl[field]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function newindex(class, field, value)
|
||||||
|
class.__vtbl[field] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
local function release(obj)
|
||||||
|
object_release(obj.__class, obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function defer(obj)
|
||||||
|
object_defer(obj.__class, obj)
|
||||||
|
end
|
||||||
|
|
||||||
|
local classMT = {
|
||||||
|
__call = new,
|
||||||
|
__index = index,
|
||||||
|
__newindex = newindex,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function invoke(object, method, ...)
|
||||||
|
local class = object.__class
|
||||||
|
for _, mixin in ipairs(class.__mixins) do
|
||||||
|
local mixin_method = mixin[method]
|
||||||
|
if mixin_method then
|
||||||
|
local ok, res = pcall(mixin_method, object, ...)
|
||||||
|
if not ok then
|
||||||
|
error(sformat("mixin: %s invoke '%s' failed: %s.", mixin.__moudle, method, res))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 返回true表示所有接口都完成
|
||||||
|
local function collect(object, method, ...)
|
||||||
|
local class = object.__class
|
||||||
|
for _, mixin in ipairs(class.__mixins) do
|
||||||
|
local mixin_method = mixin[method]
|
||||||
|
if mixin_method then
|
||||||
|
local ok, res = pcall(mixin_method, object, ...)
|
||||||
|
if not ok then
|
||||||
|
error(sformat("mixin: %s collect '%s' failed: %s.", mixin.__moudle, method, res))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if not res then
|
||||||
|
skynet.error(sformat("mixin: %s collect '%s' failed: %s.", mixin.__moudle, method, res))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 代理一个类的所有接口,并检测接口是否实现
|
||||||
|
local function implemented(class, mixins)
|
||||||
|
class.invoke = invoke
|
||||||
|
class.collect = collect
|
||||||
|
for _, mixin in ipairs(mixins) do
|
||||||
|
-- 属性处理
|
||||||
|
for name, value in pairs(mixin.__default) do
|
||||||
|
if class.__default[name] then
|
||||||
|
skynet.error(sformat("the mixin default %s has repeat defined.", name))
|
||||||
|
end
|
||||||
|
class.__default[name] = value
|
||||||
|
local access_prefix = {"is_", "get_", "set_"}
|
||||||
|
for _, prefix in pairs(access_prefix) do
|
||||||
|
local access_method = prefix .. name
|
||||||
|
if mixin[access_method] then
|
||||||
|
tinsert(mixin.__methods, access_method)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, method in pairs(mixin.__methods) do
|
||||||
|
if not mixin[method] then
|
||||||
|
skynet.error(sformat("the mixin method %s hasn't implemented.", method))
|
||||||
|
mixin[method] = function()
|
||||||
|
skynet.error(sformat("the mixin method %s hasn't implemented.", method))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if class[method] then
|
||||||
|
skynet.error(sformat("the mixin method %s has repeat implemented.", method))
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
-- 接口代理
|
||||||
|
class[method] = function(...)
|
||||||
|
return mixin[method](...)
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
tinsert(class.__mixins, mixin)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function class_constructor(class, super, ...)
|
||||||
|
local info = dgetinfo(2, "S")
|
||||||
|
local moudle = info.short_src
|
||||||
|
local class_tpl = class_tpls[moudle]
|
||||||
|
if not class_tpl then
|
||||||
|
local vtbl = {
|
||||||
|
__class = class,
|
||||||
|
__moudle = moudle,
|
||||||
|
__tostring = object_tostring,
|
||||||
|
}
|
||||||
|
vtbl.__gc = release
|
||||||
|
vtbl.__close = defer
|
||||||
|
vtbl.__index = vtbl
|
||||||
|
if super then
|
||||||
|
setmetatable(vtbl, {
|
||||||
|
__index = super,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
class.__vtbl = vtbl
|
||||||
|
class.__super = super
|
||||||
|
class.__default = {}
|
||||||
|
class.__mixins = {}
|
||||||
|
class_tpl = setmetatable(class, classMT)
|
||||||
|
implemented(class, {...})
|
||||||
|
class_tpls[moudle] = class_tpl
|
||||||
|
end
|
||||||
|
return class_tpl
|
||||||
|
end
|
||||||
|
|
||||||
|
function class(super, ...)
|
||||||
|
return class_constructor({}, super, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function singleton(super, ...)
|
||||||
|
return class_constructor({
|
||||||
|
__singleton = true,
|
||||||
|
}, super, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- function super(class)
|
||||||
|
-- return rawget(class, "__super")
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- function classof(object)
|
||||||
|
-- return object.__class
|
||||||
|
-- end
|
||||||
|
|
||||||
|
is_class = function(class)
|
||||||
|
return classMT == getmetatable(class)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_subclass(class, super)
|
||||||
|
while class do
|
||||||
|
if class == super then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
class = rawget(class, "__super")
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function instanceof(object, class)
|
||||||
|
if not object or not class then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local obj_class = object.__class
|
||||||
|
if obj_class then
|
||||||
|
return is_subclass(obj_class, class)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- function conv_class(name)
|
||||||
|
-- local runtime = sformat("local obj = %s() return obj", name)
|
||||||
|
-- local ok, obj = pcall(load(runtime))
|
||||||
|
-- if ok then
|
||||||
|
-- return obj
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
local type = type
|
-- 支持 function 和 对象内部 handler 不过后者热更会更友好
|
||||||
|
|
||||||
return function (method, obj, params)
|
return function (method, obj, params)
|
||||||
return function(...)
|
return function(...)
|
||||||
if type(method) == "string" then
|
if type(method) == "string" then
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
require "oop.class"
|
||||||
|
require "oop.mixin"
|
||||||
|
require "oop.property"
|
||||||
|
require "oop.enum"
|
||||||
|
|
||||||
|
handler = require "oop.handler"
|
||||||
|
Option = require "oop.option"
|
||||||
|
Try = require "oop.try"
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
require "oop.init"
|
||||||
|
|
||||||
|
local IObject = mixin("test1", "test2", "test3", "test4")
|
||||||
|
|
||||||
|
local prop = Property(IObject)
|
||||||
|
prop:accessor("key1", 1)
|
||||||
|
prop:accessor("key2", 2)
|
||||||
|
|
||||||
|
function IObject:__init()
|
||||||
|
end
|
||||||
|
|
||||||
|
function IObject:test1()
|
||||||
|
print("key1", self:get_key1())
|
||||||
|
self:set_key2(4)
|
||||||
|
print("key2", self:get_key2())
|
||||||
|
self:set_key3(6)
|
||||||
|
print("key3", self:get_key3())
|
||||||
|
end
|
||||||
|
|
||||||
|
function IObject:test2()
|
||||||
|
print("key2", self.key2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function IObject:test3()
|
||||||
|
print("key3", self.key3)
|
||||||
|
end
|
||||||
|
|
||||||
|
local Object = class(nil, IObject)
|
||||||
|
local prop2 = Property(Object)
|
||||||
|
prop2:accessor("key3", 3)
|
||||||
|
function Object:__init()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Object:__release()
|
||||||
|
print("release", self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Object:run()
|
||||||
|
print("key3", self:get_key3())
|
||||||
|
print("key1", self:get_key1())
|
||||||
|
print("key2", self:get_key2())
|
||||||
|
self:invoke("test1")
|
||||||
|
end
|
||||||
|
|
||||||
|
local TEST1 = Enum("TEST1", 0, "ONE", "THREE", "TWO")
|
||||||
|
print(TEST1.TWO)
|
||||||
|
local TEST2 = Enum("TEST2", 1, "ONE", "THREE", "TWO")
|
||||||
|
TEST2.FOUR = TEST2()
|
||||||
|
print(TEST2.TWO, TEST2.FOUR)
|
||||||
|
local TEST3 = Enum("TEST3", 0)
|
||||||
|
TEST3("ONE")
|
||||||
|
TEST3("TWO")
|
||||||
|
TEST3("FOUR", 4)
|
||||||
|
local five = TEST3("FIVE")
|
||||||
|
print(TEST3.TWO, TEST3.FOUR, TEST3.FIVE, five)
|
||||||
|
|
||||||
|
local obj = Object()
|
||||||
|
obj:run()
|
||||||
|
|
||||||
|
return Object
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
--[[property.lua
|
||||||
|
local Object = class()
|
||||||
|
prop = property(Object)
|
||||||
|
prop:reader("id", 0)
|
||||||
|
prop:accessor("name", "")
|
||||||
|
--]]
|
||||||
|
local ACCESSOR = 1
|
||||||
|
local WRITER = 2
|
||||||
|
local READER = 3
|
||||||
|
|
||||||
|
local function prop_accessor(_, class, name, default, mode, cb)
|
||||||
|
class.__default[name] = {default}
|
||||||
|
if mode <= WRITER then
|
||||||
|
class["set_" .. name] = function(self, value)
|
||||||
|
if self[name] == nil or self[name] ~= value then
|
||||||
|
self[name] = value
|
||||||
|
if cb then
|
||||||
|
cb(self, name, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mode = mode + 2
|
||||||
|
end
|
||||||
|
if mode <= READER then
|
||||||
|
class["get_" .. name] = function(self)
|
||||||
|
if self[name] == nil then
|
||||||
|
return default
|
||||||
|
end
|
||||||
|
return self[name]
|
||||||
|
end
|
||||||
|
if type(default) == "boolean" then
|
||||||
|
class["is_" .. name] = class["get_" .. name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local property_reader = function(self, name, default)
|
||||||
|
prop_accessor(self, self.__class, name, default, READER)
|
||||||
|
end
|
||||||
|
local property_writer = function(self, name, default, cb)
|
||||||
|
prop_accessor(self, self.__class, name, default, WRITER, cb)
|
||||||
|
end
|
||||||
|
local property_accessor = function(self, name, default, cb)
|
||||||
|
prop_accessor(self, self.__class, name, default, ACCESSOR, cb)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Property(class)
|
||||||
|
local prop = {
|
||||||
|
__class = class,
|
||||||
|
reader = property_reader,
|
||||||
|
writer = property_writer,
|
||||||
|
accessor = property_accessor,
|
||||||
|
}
|
||||||
|
return prop
|
||||||
|
end
|
||||||
|
|
||||||
@ -1,473 +0,0 @@
|
|||||||
--[[
|
|
||||||
Minimal test framework for Lua.
|
|
||||||
lester - v0.1.2 - 15/Feb/2021
|
|
||||||
Eduardo Bart - edub4rt@gmail.com
|
|
||||||
https://github.com/edubart/lester
|
|
||||||
Minimal Lua test framework.
|
|
||||||
See end of file for LICENSE.
|
|
||||||
]] --[[--
|
|
||||||
Lester is a minimal unit testing framework for Lua with a focus on being simple to use.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
* Minimal, just one file.
|
|
||||||
* Self contained, no external dependencies.
|
|
||||||
* Simple and hackable when needed.
|
|
||||||
* Use `describe` and `it` blocks to describe tests.
|
|
||||||
* Supports `before` and `after` handlers.
|
|
||||||
* Colored output.
|
|
||||||
* Configurable via the script or with environment variables.
|
|
||||||
* Quiet mode, to use in live development.
|
|
||||||
* Optionally filter tests by name.
|
|
||||||
* Show traceback on errors.
|
|
||||||
* Show time to complete tests.
|
|
||||||
* Works with Lua 5.1+.
|
|
||||||
* Efficient.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Copy `lester.lua` file to a project and require it,
|
|
||||||
which returns a table that includes all of the functionality:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local lester = require 'lester'
|
|
||||||
local describe, it, expect = lester.describe, lester.it, lester.expect
|
|
||||||
|
|
||||||
-- Customize lester configuration.
|
|
||||||
lester.show_traceback = false
|
|
||||||
|
|
||||||
describe('my project', function()
|
|
||||||
lester.before(function()
|
|
||||||
-- This function is run before every test.
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe('module1', function() -- Describe blocks can be nested.
|
|
||||||
it('feature1', function()
|
|
||||||
expect.equal('something', 'something') -- Pass.
|
|
||||||
end)
|
|
||||||
|
|
||||||
it('feature2', function()
|
|
||||||
expect.truthy(false) -- Fail.
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
lester.report() -- Print overall statistic of the tests run.
|
|
||||||
lester.exit() -- Exit with success if all tests passed.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Customizing output with environment variables
|
|
||||||
|
|
||||||
To customize the output of lester externally,
|
|
||||||
you can set the following environment variables before running a test suite:
|
|
||||||
|
|
||||||
* `LESTER_QUIET="true"`, omit print of passed tests.
|
|
||||||
* `LESTER_COLORED="false"`, disable colored output.
|
|
||||||
* `LESTER_SHOW_TRACEBACK="false"`, disable traceback on test failures.
|
|
||||||
* `LESTER_SHOW_ERROR="false"`, omit print of error description of failed tests.
|
|
||||||
* `LESTER_STOP_ON_FAIL="true"`, stop on first test failure.
|
|
||||||
* `LESTER_UTF8TERM="false"`, disable printing of UTF-8 characters.
|
|
||||||
* `LESTER_FILTER="some text"`, filter the tests that should be run.
|
|
||||||
|
|
||||||
Note that these configurations can be changed via script too, check the documentation.
|
|
||||||
|
|
||||||
]] -- Returns whether the terminal supports UTF-8 characters.
|
|
||||||
local function is_utf8term()
|
|
||||||
local lang = os.getenv('LANG')
|
|
||||||
return (lang and lang:lower():match('utf%-8$')) and true or false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Returns whether a system environment variable is "true".
|
|
||||||
local function getboolenv(varname, default)
|
|
||||||
local val = os.getenv(varname)
|
|
||||||
if val == 'true' then
|
|
||||||
return true
|
|
||||||
elseif val == 'false' then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
return default
|
|
||||||
end
|
|
||||||
|
|
||||||
-- The lester module.
|
|
||||||
local lester = {
|
|
||||||
--- Weather lines of passed tests should not be printed. False by default.
|
|
||||||
quiet = getboolenv('LESTER_QUIET', false),
|
|
||||||
--- Weather the output should be colorized. True by default.
|
|
||||||
colored = getboolenv('LESTER_COLORED', true),
|
|
||||||
--- Weather a traceback must be shown on test failures. True by default.
|
|
||||||
show_traceback = getboolenv('LESTER_SHOW_TRACEBACK', true),
|
|
||||||
--- Weather the error description of a test failure should be shown. True by default.
|
|
||||||
show_error = getboolenv('LESTER_SHOW_ERROR', true),
|
|
||||||
--- Weather test suite should exit on first test failure. False by default.
|
|
||||||
stop_on_fail = getboolenv('LESTER_STOP_ON_FAIL', false),
|
|
||||||
--- Weather we can print UTF-8 characters to the terminal. True by default when supported.
|
|
||||||
utf8term = getboolenv('LESTER_UTF8TERM', is_utf8term()),
|
|
||||||
--- A string with a lua pattern to filter tests. Nil by default.
|
|
||||||
filter = os.getenv('LESTER_FILTER'),
|
|
||||||
--- Function to retrieve time in seconds with milliseconds precision, `os.clock` by default.
|
|
||||||
seconds = os.clock,
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Variables used internally for the lester state.
|
|
||||||
local lester_start = nil
|
|
||||||
local last_succeeded = false
|
|
||||||
local level = 0
|
|
||||||
local successes = 0
|
|
||||||
local total_successes = 0
|
|
||||||
local failures = 0
|
|
||||||
local total_failures = 0
|
|
||||||
local start = 0
|
|
||||||
local befores = {}
|
|
||||||
local afters = {}
|
|
||||||
local names = {}
|
|
||||||
|
|
||||||
-- Color codes.
|
|
||||||
local color_codes = {
|
|
||||||
reset = string.char(27) .. '[0m',
|
|
||||||
bright = string.char(27) .. '[1m',
|
|
||||||
red = string.char(27) .. '[31m',
|
|
||||||
green = string.char(27) .. '[32m',
|
|
||||||
blue = string.char(27) .. '[34m',
|
|
||||||
magenta = string.char(27) .. '[35m',
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Colors table, returning proper color code if colored mode is enabled.
|
|
||||||
local colors = setmetatable({}, {
|
|
||||||
__index = function(_, key)
|
|
||||||
return lester.colored and color_codes[key] or ''
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
--- Table of terminal colors codes, can be customized.
|
|
||||||
lester.colors = colors
|
|
||||||
|
|
||||||
--- Describe a block of tests, which consists in a set of tests.
|
|
||||||
-- Describes can be nested.
|
|
||||||
-- @param name A string used to describe the block.
|
|
||||||
-- @param func A function containing all the tests or other describes.
|
|
||||||
function lester.describe(name, func)
|
|
||||||
if level == 0 then -- Get start time for top level describe blocks.
|
|
||||||
start = lester.seconds()
|
|
||||||
if not lester_start then
|
|
||||||
lester_start = start
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Setup describe block variables.
|
|
||||||
failures = 0
|
|
||||||
successes = 0
|
|
||||||
level = level + 1
|
|
||||||
names[level] = name
|
|
||||||
-- Run the describe block.
|
|
||||||
func()
|
|
||||||
-- Cleanup describe block.
|
|
||||||
afters[level] = nil
|
|
||||||
befores[level] = nil
|
|
||||||
names[level] = nil
|
|
||||||
level = level - 1
|
|
||||||
-- Pretty print statistics for top level describe block.
|
|
||||||
if level == 0 and not lester.quiet and (successes > 0 or failures > 0) then
|
|
||||||
local io_write = io.write
|
|
||||||
local colors_reset, colors_green = colors.reset, colors.green
|
|
||||||
io_write(failures == 0 and colors_green or colors.red, '[====] ', colors.magenta, name, colors_reset, ' | ',
|
|
||||||
colors_green, successes, colors_reset, ' successes / ')
|
|
||||||
if failures > 0 then
|
|
||||||
io_write(colors.red, failures, colors_reset, ' failures / ')
|
|
||||||
end
|
|
||||||
io_write(colors.bright, string.format('%.6f', lester.seconds() - start), colors_reset, ' seconds\n')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Error handler used to get traceback for errors.
|
|
||||||
local function xpcall_error_handler(err)
|
|
||||||
return debug.traceback(tostring(err), 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Pretty print the line on the test file where an error happened.
|
|
||||||
local function show_error_line(err)
|
|
||||||
local info = debug.getinfo(3)
|
|
||||||
local io_write = io.write
|
|
||||||
local colors_reset = colors.reset
|
|
||||||
local short_src, currentline = info.short_src, info.currentline
|
|
||||||
io_write(' (', colors.blue, short_src, colors_reset, ':', colors.bright, currentline, colors_reset)
|
|
||||||
if err and lester.show_traceback then
|
|
||||||
local fnsrc = short_src .. ':' .. currentline
|
|
||||||
for cap1, cap2 in err:gmatch('\t[^\n:]+:(%d+): in function <([^>]+)>\n') do
|
|
||||||
if cap2 == fnsrc then
|
|
||||||
io_write('/', colors.bright, cap1, colors_reset)
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
io_write(')')
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Pretty print the test name, with breadcrumb for the describe blocks.
|
|
||||||
local function show_test_name(name)
|
|
||||||
local io_write = io.write
|
|
||||||
local colors_reset = colors.reset
|
|
||||||
for _, descname in ipairs(names) do
|
|
||||||
io_write(colors.magenta, descname, colors_reset, ' | ')
|
|
||||||
end
|
|
||||||
io_write(colors.bright, name, colors_reset)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Declare a test, which consists of a set of assertions.
|
|
||||||
-- @param name A name for the test.
|
|
||||||
-- @param func The function containing all assertions.
|
|
||||||
function lester.it(name, func)
|
|
||||||
-- Skip the test if it does not match the filter.
|
|
||||||
if lester.filter then
|
|
||||||
local fullname = table.concat(names, ' | ') .. ' | ' .. name
|
|
||||||
if not fullname:match(lester.filter) then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Execute before handlers.
|
|
||||||
for _, levelbefores in ipairs(befores) do
|
|
||||||
for _, beforefn in ipairs(levelbefores) do
|
|
||||||
beforefn(name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Run the test, capturing errors if any.
|
|
||||||
local success, err
|
|
||||||
if lester.show_traceback then
|
|
||||||
success, err = xpcall(func, xpcall_error_handler)
|
|
||||||
else
|
|
||||||
success, err = pcall(func)
|
|
||||||
if not success and err then
|
|
||||||
err = tostring(err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Count successes and failures.
|
|
||||||
if success then
|
|
||||||
successes = successes + 1
|
|
||||||
total_successes = total_successes + 1
|
|
||||||
else
|
|
||||||
failures = failures + 1
|
|
||||||
total_failures = total_failures + 1
|
|
||||||
end
|
|
||||||
local io_write = io.write
|
|
||||||
local colors_reset = colors.reset
|
|
||||||
-- Print the test run.
|
|
||||||
if not lester.quiet then -- Show test status and complete test name.
|
|
||||||
if success then
|
|
||||||
io_write(colors.green, '[PASS] ', colors_reset)
|
|
||||||
else
|
|
||||||
io_write(colors.red, '[FAIL] ', colors_reset)
|
|
||||||
end
|
|
||||||
show_test_name(name)
|
|
||||||
if not success then
|
|
||||||
show_error_line(err)
|
|
||||||
end
|
|
||||||
io_write('\n')
|
|
||||||
else
|
|
||||||
if success then -- Show just a character hinting that the test succeeded.
|
|
||||||
local o = (lester.utf8term and lester.colored) and string.char(226, 151, 143) or 'o'
|
|
||||||
io_write(colors.green, o, colors_reset)
|
|
||||||
else -- Show complete test name on failure.
|
|
||||||
io_write(last_succeeded and '\n' or '', colors.red, '[FAIL] ', colors_reset)
|
|
||||||
show_test_name(name)
|
|
||||||
show_error_line(err)
|
|
||||||
io_write('\n')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Print error message, colorizing its output if possible.
|
|
||||||
if err and lester.show_error then
|
|
||||||
if lester.colored then
|
|
||||||
local errfile, errline, errmsg, rest = err:match('^([^:\n]+):(%d+): ([^\n]+)(.*)')
|
|
||||||
if errfile and errline and errmsg and rest then
|
|
||||||
io_write(colors.blue, errfile, colors_reset, ':', colors.bright, errline, colors_reset, ': ')
|
|
||||||
if errmsg:match('^%w([^:]*)$') then
|
|
||||||
io_write(colors.red, errmsg, colors_reset)
|
|
||||||
else
|
|
||||||
io_write(errmsg)
|
|
||||||
end
|
|
||||||
err = rest
|
|
||||||
end
|
|
||||||
end
|
|
||||||
io_write(err, '\n\n')
|
|
||||||
end
|
|
||||||
io.flush()
|
|
||||||
-- Stop on failure.
|
|
||||||
if not success and lester.stop_on_fail then
|
|
||||||
if lester.quiet then
|
|
||||||
io_write('\n')
|
|
||||||
io.flush()
|
|
||||||
end
|
|
||||||
lester.exit()
|
|
||||||
end
|
|
||||||
-- Execute after handlers.
|
|
||||||
for _, levelafters in ipairs(afters) do
|
|
||||||
for _, afterfn in ipairs(levelafters) do
|
|
||||||
afterfn(name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
last_succeeded = success
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Set a function that is called before every test inside a describe block.
|
|
||||||
-- A single string containing the name of the test about to be run will be passed to `func`.
|
|
||||||
function lester.before(func)
|
|
||||||
local levelbefores = befores[level]
|
|
||||||
if not levelbefores then
|
|
||||||
levelbefores = {}
|
|
||||||
befores[level] = levelbefores
|
|
||||||
end
|
|
||||||
levelbefores[#levelbefores + 1] = func
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Set a function that is called after every test inside a describe block.
|
|
||||||
-- A single string containing the name of the test that was finished will be passed to `func`.
|
|
||||||
-- The function is executed independently if the test passed or failed.
|
|
||||||
function lester.after(func)
|
|
||||||
local levelafters = afters[level]
|
|
||||||
if not levelafters then
|
|
||||||
levelafters = {}
|
|
||||||
afters[level] = levelafters
|
|
||||||
end
|
|
||||||
levelafters[#levelafters + 1] = func
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Pretty print statistics of all test runs.
|
|
||||||
-- With total success, total failures and run time in seconds.
|
|
||||||
function lester.report()
|
|
||||||
local now = lester.seconds()
|
|
||||||
local colors_reset = colors.reset
|
|
||||||
io.write(lester.quiet and '\n' or '', colors.green, total_successes, colors_reset, ' successes / ', colors.red,
|
|
||||||
total_failures, colors_reset, ' failures / ', colors.bright, string.format('%.6f', now - (lester_start or now)),
|
|
||||||
colors_reset, ' seconds\n')
|
|
||||||
io.flush()
|
|
||||||
return total_failures == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Exit the application with success code if all tests passed, or failure code otherwise.
|
|
||||||
function lester.exit()
|
|
||||||
os.exit(total_failures == 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
local expect = {}
|
|
||||||
--- Expect module, containing utility function for doing assertions inside a test.
|
|
||||||
lester.expect = expect
|
|
||||||
|
|
||||||
--- Check if a function fails with an error.
|
|
||||||
-- If `expected` is nil then any error is accepted.
|
|
||||||
-- If `expected` is a string then we check if the error contains that string.
|
|
||||||
-- If `expected` is anything else then we check if both are equal.
|
|
||||||
function expect.fail(func, expected)
|
|
||||||
local ok, err = pcall(func)
|
|
||||||
if ok then
|
|
||||||
error('expected function to fail', 2)
|
|
||||||
elseif expected ~= nil then
|
|
||||||
local found = expected == err
|
|
||||||
if not found and type(expected) == 'string' then
|
|
||||||
found = string.find(tostring(err), expected, 1, true)
|
|
||||||
end
|
|
||||||
if not found then
|
|
||||||
error('expected function to fail\nexpected:\n' .. tostring(expected) .. '\ngot:\n' .. tostring(err), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if a function does not fail with a error.
|
|
||||||
function expect.not_fail(func)
|
|
||||||
local ok, err = pcall(func)
|
|
||||||
if not ok then
|
|
||||||
error('expected function to not fail\ngot error:\n' .. tostring(err), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if a value is not `nil`.
|
|
||||||
function expect.exist(v)
|
|
||||||
if v == nil then
|
|
||||||
error('expected value to exist\ngot:\n' .. tostring(v), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if a value is `nil`.
|
|
||||||
function expect.not_exist(v)
|
|
||||||
if v ~= nil then
|
|
||||||
error('expected value to not exist\ngot:\n' .. tostring(v), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if an expression is evaluates to `true`.
|
|
||||||
function expect.truthy(v)
|
|
||||||
if not v then
|
|
||||||
error('expected expression to be true\ngot:\n' .. tostring(v), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if an expression is evaluates to `false`.
|
|
||||||
function expect.falsy(v)
|
|
||||||
if v then
|
|
||||||
error('expected expression to be false\ngot:\n' .. tostring(v), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Compare if two values are equal, considering nested tables.
|
|
||||||
local function strict_eq(t1, t2)
|
|
||||||
if rawequal(t1, t2) then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
if type(t1) ~= type(t2) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if type(t1) ~= 'table' then
|
|
||||||
return t1 == t2
|
|
||||||
end
|
|
||||||
if getmetatable(t1) ~= getmetatable(t2) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
for k, v1 in pairs(t1) do
|
|
||||||
if not strict_eq(v1, t2[k]) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for k, v2 in pairs(t2) do
|
|
||||||
if not strict_eq(v2, t1[k]) then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if two values are equal.
|
|
||||||
function expect.equal(v1, v2)
|
|
||||||
if not strict_eq(v1, v2) then
|
|
||||||
error('expected values to be equal\nfirst value:\n' .. tostring(v1) .. '\nsecond value:\n' .. tostring(v2), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Check if two values are not equal.
|
|
||||||
function expect.not_equal(v1, v2)
|
|
||||||
if strict_eq(v1, v2) then
|
|
||||||
error('expected values to be not equal\nfirst value:\n' .. tostring(v1) .. '\nsecond value:\n' .. tostring(v2),
|
|
||||||
2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return lester
|
|
||||||
|
|
||||||
--[[
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2021 Eduardo Bart (https://github.com/edubart)
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
]]
|
|
||||||
Loading…
Reference in New Issue