mirror of
https://github.com/kikito/middleclass.git
synced 2024-11-25 02:44:20 +00:00
merged branch 2.0
This commit is contained in:
commit
3f9ae1d6e8
@ -22,5 +22,4 @@ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -5,7 +5,7 @@ Lua OOP classes usually end being:
|
||||
* multi-file libraries, too difficult to understand
|
||||
* very small libraries, not very powerful
|
||||
|
||||
Middleclass attemps to be a mid-sized library (~120 lines of code, on a single file), with clean, easy to understand code, and yet powerful enough to be used in most cases.
|
||||
Middleclass attemps to be a mid-sized library (~130 lines of code, on a single file), with clean, easy to understand code, and yet powerful enough to be used in most cases.
|
||||
|
||||
h1. Documentation
|
||||
|
||||
@ -13,12 +13,13 @@ See the "github wiki page":https://github.com/kikito/middleclass/wiki for exampl
|
||||
|
||||
h1. Features
|
||||
|
||||
* ~120 lines of code
|
||||
* ~130 lines of code
|
||||
* top-level Object class
|
||||
* all methods are virtual
|
||||
* instance.class returns the instance's class
|
||||
* @Class.name@ returns the class name (a string)
|
||||
* @Class.superclass@ returns its super class
|
||||
* @Class.super@ returns its super class
|
||||
* Class methods can be defined with Class.static.methodname
|
||||
* Subclassing:
|
||||
** @class(name)@ creates a subclass of @Object@
|
||||
** @class(name, Superclass)@ creates a subclass of the class @SuperClass@
|
||||
@ -43,47 +44,19 @@ Features left out:
|
||||
* classes are not Objects (instances are)
|
||||
* simulating a 'super' keyword (for performance concerns)
|
||||
|
||||
h1. Installation using git (recommended)
|
||||
h1. Installation
|
||||
|
||||
The easiest way is creating a clone using git:
|
||||
|
||||
<pre>git clone git://github.com/kikito/middleclass.git</pre>
|
||||
|
||||
This will create a folder called @middleclass@. You can require it using:
|
||||
|
||||
<pre>require 'middleclass.init'</pre>
|
||||
|
||||
If you have @?/init.lua@ included in your @package.path@ variable, you can remove the @.init@ part.
|
||||
|
||||
If you ever want to update middleclass, you can use the following commands:
|
||||
|
||||
<pre>cd your/path/to/middleclass
|
||||
git pull origin master</pre>
|
||||
|
||||
Git will automatically update middleclass for you.
|
||||
|
||||
h1. Manual download
|
||||
|
||||
Middleclass can be directly downloaded using "the github downloader":https://github.com/kikito/middleclass/archives/master
|
||||
|
||||
You will not be able to get automatic updates with that method, but your folder size will be smaller.
|
||||
|
||||
As an alternative, you can directly download "middleclass.lua":https://github.com/kikito/middleclass/raw/master/middleclass.lua and "BSD-LICENSE.txt":https://github.com/kikito/middleclass/raw/master/BSD-LICENSE.txt and put them in your project.
|
||||
|
||||
In that case, you will have to require it doing something like this (will vary if you put middleclass.lua inside a folder):
|
||||
Just copy the middleclass.lua file wherever you want it (for example on a lib/ folder). Then write this in any Lua file where you want to use it:
|
||||
|
||||
<pre>require 'middleclass'</pre>
|
||||
|
||||
(This assumes you have @?.lua@ in your @package.path@, which is the default)
|
||||
|
||||
You will have to put BSD-LICENSE.txt somewhere in your folder, or copy-paste it inside of your own license.txt file.
|
||||
|
||||
h1. middleclass-extras
|
||||
|
||||
This library has a companion lib that adds a lot of interesting functionality to your objects. Give it a look at "middleclass-extras":https://github.com/kikito/middleclass-extras
|
||||
|
||||
If you are looking for @MindState@ (now called @Stateful@), it's over there, too.
|
||||
Make sure that you read the license, too. In order to comply with it you will have to include its full text inside your program's documentation, or else make it visible in the app's credits.
|
||||
|
||||
h1. Specs
|
||||
|
||||
You may find the specs for this library in "middleclass-specs":https://github.com/kikito/middleclass-specs
|
||||
This project uses "telescope":https://github.com/norman/telescope for its specs. If you want to run the specs, you will have to install telescope first. Then just execute the following from the root inspect folder:
|
||||
|
||||
<pre>
|
||||
tsc -f spec/*
|
||||
</pre>
|
||||
|
||||
|
258
middleclass.lua
258
middleclass.lua
@ -1,120 +1,138 @@
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
-- middleclass.lua - v1.4 (2011-03)
|
||||
-- Enrique García Cota - enrique.garcia.cota [AT] gmail [DOT] com
|
||||
-- Based on YaciCode, from Julien Patte and LuaObject, from Sébastien Rocca-Serra
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
local _nilf = function() end -- empty function
|
||||
|
||||
local _classes = setmetatable({}, {__mode = "k"}) -- keeps track of all the tables that are classes
|
||||
|
||||
Object = { name = "Object", __mixins = {} }
|
||||
|
||||
Object.__classDict = {
|
||||
initialize = _nilf, destroy = _nilf, subclassed = _nilf,
|
||||
__tostring = function(instance) return ("instance of ".. instance.class.name) end, -- root of __tostring method,
|
||||
__metamethods = { '__add', '__call', '__concat', '__div', '__le', '__lt',
|
||||
'__mod', '__mul', '__pow', '__sub', '__tostring', '__unm'
|
||||
}
|
||||
}
|
||||
Object.__classDict.__index = Object.__classDict -- instances of Object need this
|
||||
|
||||
setmetatable(Object, {
|
||||
__index = Object.__classDict, -- look up methods in the classDict
|
||||
__newindex = Object.__classDict, -- any new Object methods will be defined in classDict
|
||||
__call = Object.new, -- allows instantiation via Object()
|
||||
__tostring = function() return "class Object" end -- allows tostring(obj)
|
||||
})
|
||||
|
||||
_classes[Object] = true -- register Object on the list of classes.
|
||||
|
||||
-- creates the instance based of the class, but doesn't initialize it
|
||||
function Object.allocate(theClass)
|
||||
assert(_classes[theClass], "Use Class:allocate instead of Class.allocate")
|
||||
return setmetatable({ class = theClass }, theClass.__classDict)
|
||||
end
|
||||
|
||||
-- both creates and initializes an instance
|
||||
function Object.new(theClass, ...)
|
||||
local instance = theClass:allocate()
|
||||
instance:initialize(...)
|
||||
return instance
|
||||
end
|
||||
|
||||
-- creates a subclass
|
||||
function Object.subclass(theClass, name)
|
||||
assert(_classes[theClass], "Use Class:subclass instead of Class.subclass")
|
||||
assert( type(name)=="string", "You must provide a name(string) for your class")
|
||||
|
||||
local theSubClass = { name = name, superclass = theClass, __classDict = {}, __mixins={} }
|
||||
|
||||
local dict = theSubClass.__classDict -- classDict contains all the [meta]methods of the class
|
||||
dict.__index = dict -- It "points to itself" so instances can use it as a metatable.
|
||||
local superDict = theClass.__classDict -- The superclass' classDict
|
||||
|
||||
setmetatable(dict, superDict) -- when a method isn't found on classDict, 'escalate upwards'.
|
||||
|
||||
setmetatable(theSubClass, {
|
||||
__index = dict, -- look for stuff on the dict
|
||||
__newindex = function(_, methodName, method) -- ensure that __index isn't modified by mistake
|
||||
assert(methodName ~= '__index', "Can't modify __index. Include middleclass-extras.Indexable and use 'index' instead")
|
||||
rawset(dict, methodName , method)
|
||||
end,
|
||||
__tostring = function() return ("class ".. name) end, -- allows tostring(MyClass)
|
||||
__call = function(_, ...) return theSubClass:new(...) end -- allows MyClass(...) instead of MyClass:new(...)
|
||||
})
|
||||
|
||||
for _,mmName in ipairs(theClass.__metamethods) do -- Creates the initial metamethods
|
||||
dict[mmName]= function(...) -- by default, they just 'look up' for an implememtation
|
||||
local method = superDict[mmName] -- and if none found, they throw an error
|
||||
assert( type(method)=='function', tostring(theSubClass) .. " doesn't implement metamethod '" .. mmName .. "'" )
|
||||
return method(...)
|
||||
end
|
||||
end
|
||||
|
||||
theSubClass.initialize = function(instance,...) theClass.initialize(instance, ...) end
|
||||
_classes[theSubClass]= true -- registers the new class on the list of _classes
|
||||
theClass:subclassed(theSubClass) -- hook method. By default it does nothing
|
||||
|
||||
return theSubClass
|
||||
end
|
||||
|
||||
-- Mixin extension function - simulates very basically ruby's include. Receives a table table, probably with functions.
|
||||
-- Its contents are copied to theClass, with one exception: the included() method will be called instead of copied
|
||||
function Object.include(theClass, mixin, ... )
|
||||
assert(_classes[theClass], "Use class:include instead of class.include")
|
||||
assert(type(mixin)=='table', "mixin must be a table")
|
||||
for methodName,method in pairs(mixin) do
|
||||
if methodName ~="included" then theClass[methodName] = method end
|
||||
end
|
||||
if type(mixin.included)=="function" then mixin:included(theClass, ... ) end
|
||||
theClass.__mixins[mixin] = mixin
|
||||
return theClass
|
||||
end
|
||||
|
||||
-- Returns true if aClass is a subclass of other, false otherwise
|
||||
function subclassOf(other, aClass)
|
||||
if not _classes[aClass] or not _classes[other] then return false end
|
||||
if aClass.superclass==nil then return false end -- aClass is Object, or a non-class
|
||||
return aClass.superclass == other or subclassOf(other, aClass.superclass)
|
||||
end
|
||||
|
||||
-- Returns true if obj is an instance of aClass (or one of its subclasses) false otherwise
|
||||
function instanceOf(aClass, obj)
|
||||
if not _classes[aClass] or type(obj)~='table' or not _classes[obj.class] then return false end
|
||||
if obj.class==aClass then return true end
|
||||
return subclassOf(aClass, obj.class)
|
||||
end
|
||||
|
||||
-- Returns true if the mixin has already been included on a class (or a superclass)
|
||||
function includes(mixin, aClass)
|
||||
if not _classes[aClass] then return false end
|
||||
if aClass.__mixins[mixin]==mixin then return true end
|
||||
return includes(mixin, aClass.superclass)
|
||||
end
|
||||
|
||||
-- Creates a new class named 'name'. Uses Object if no baseClass is specified.
|
||||
function class(name, baseClass, ...)
|
||||
baseClass = baseClass or Object
|
||||
return baseClass:subclass(name, ...)
|
||||
end
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
-- middleclass.lua - v2.0 (2011-08)
|
||||
-- Enrique Garcia Cota - enrique.garcia.cota [AT] gmail [DOT] com
|
||||
-- Based on YaciCode, from Julien Patte and LuaObject, from Sebastien Rocca-Serra
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
local _classes = setmetatable({}, {__mode = "k"})
|
||||
|
||||
local function _setClassDictionariesMetatables(klass)
|
||||
local dict = klass.__instanceDict
|
||||
dict.__index = dict
|
||||
|
||||
local super = klass.super
|
||||
if super then
|
||||
local superStatic = super.static
|
||||
setmetatable(dict, super.__instanceDict)
|
||||
setmetatable(klass.static, { __index = function(_,k) return dict[k] or superStatic[k] end })
|
||||
else
|
||||
setmetatable(klass.static, { __index = function(_,k) return dict[k] end })
|
||||
end
|
||||
end
|
||||
|
||||
local function _setClassMetatable(klass)
|
||||
setmetatable(klass, {
|
||||
__tostring = function() return "class " .. klass.name end,
|
||||
__index = klass.static,
|
||||
__newindex = klass.__instanceDict,
|
||||
__call = function(self, ...) return self:new(...) end
|
||||
})
|
||||
end
|
||||
|
||||
local function _createClass(name, super)
|
||||
local klass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} }
|
||||
klass.subclasses = setmetatable({}, {__mode = "k"})
|
||||
|
||||
_setClassDictionariesMetatables(klass)
|
||||
_setClassMetatable(klass)
|
||||
_classes[klass] = true
|
||||
|
||||
return klass
|
||||
end
|
||||
|
||||
local function _createLookupMetamethod(klass, name)
|
||||
return function(...)
|
||||
local method = klass.super[name]
|
||||
assert( type(method)=='function', tostring(klass) .. " doesn't implement metamethod '" .. name .. "'" )
|
||||
return method(...)
|
||||
end
|
||||
end
|
||||
|
||||
local function _setClassMetamethods(klass)
|
||||
for _,m in ipairs(klass.__metamethods) do
|
||||
klass[m]= _createLookupMetamethod(klass, m)
|
||||
end
|
||||
end
|
||||
|
||||
local function _setDefaultInitializeMethod(klass, super)
|
||||
klass.initialize = function(instance, ...)
|
||||
return super.initialize(instance, ...)
|
||||
end
|
||||
end
|
||||
|
||||
local function _includeMixin(klass, mixin)
|
||||
assert(type(mixin)=='table', "mixin must be a table")
|
||||
for name,method in pairs(mixin) do
|
||||
if name ~= "included" and name ~= "static" then klass[name] = method end
|
||||
end
|
||||
if mixin.static then
|
||||
for name,method in pairs(mixin.static) do
|
||||
klass.static[name] = method
|
||||
end
|
||||
end
|
||||
if type(mixin.included)=="function" then mixin:included(klass) end
|
||||
klass.__mixins[mixin] = true
|
||||
end
|
||||
|
||||
Object = _createClass("Object", nil)
|
||||
|
||||
Object.static.__metamethods = { '__add', '__call', '__concat', '__div', '__le', '__lt',
|
||||
'__mod', '__mul', '__pow', '__sub', '__tostring', '__unm' }
|
||||
|
||||
function Object.static:allocate()
|
||||
assert(_classes[self], "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
|
||||
return setmetatable({ class = self }, self.__instanceDict)
|
||||
end
|
||||
|
||||
function Object.static:new(...)
|
||||
local instance = self:allocate()
|
||||
instance:initialize(...)
|
||||
return instance
|
||||
end
|
||||
|
||||
function Object.static:subclass(name)
|
||||
assert(_classes[self], "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)
|
||||
_setClassMetamethods(subclass)
|
||||
_setDefaultInitializeMethod(subclass, self)
|
||||
self.subclasses[subclass] = true
|
||||
self:subclassed(subclass)
|
||||
|
||||
return subclass
|
||||
end
|
||||
|
||||
function Object.static:subclassed(other) end
|
||||
|
||||
function Object.static:include( ... )
|
||||
assert(_classes[self], "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 Object:initialize() end
|
||||
|
||||
function Object:__tostring() return "instance of " .. tostring(self.class) end
|
||||
|
||||
function class(name, super, ...)
|
||||
super = super or Object
|
||||
return super:subclass(name, ...)
|
||||
end
|
||||
|
||||
function instanceOf(aClass, obj)
|
||||
if not _classes[aClass] or type(obj) ~= 'table' or not _classes[obj.class] then return false end
|
||||
if obj.class == aClass then return true end
|
||||
return subclassOf(aClass, obj.class)
|
||||
end
|
||||
|
||||
function subclassOf(other, aClass)
|
||||
if not _classes[aClass] or not _classes[other] or aClass.super == nil then return false end
|
||||
return aClass.super == other or subclassOf(other, aClass.super)
|
||||
end
|
||||
|
||||
function includes(mixin, aClass)
|
||||
if not _classes[aClass] then return false end
|
||||
if aClass.__mixins[mixin] then return true end
|
||||
return includes(mixin, aClass.super)
|
||||
end
|
||||
|
43
performance/run.lua
Normal file
43
performance/run.lua
Normal file
@ -0,0 +1,43 @@
|
||||
require 'middleclass'
|
||||
|
||||
time = require 'performance/time'
|
||||
|
||||
time('class creation', function()
|
||||
local A = class('A')
|
||||
end)
|
||||
|
||||
local A = class('A')
|
||||
|
||||
time('instance creation', function()
|
||||
local a = A:new()
|
||||
end)
|
||||
|
||||
function A:foo()
|
||||
return 1
|
||||
end
|
||||
|
||||
local a = A:new()
|
||||
|
||||
time('instance method invocation', function()
|
||||
a:foo()
|
||||
end)
|
||||
|
||||
local B = class('B', A)
|
||||
|
||||
local b = B:new()
|
||||
|
||||
time('inherited method invocation', function()
|
||||
b:foo()
|
||||
end)
|
||||
|
||||
function A.static:bar()
|
||||
return 2
|
||||
end
|
||||
|
||||
time('class method invocation', function()
|
||||
A:bar()
|
||||
end)
|
||||
|
||||
time('inherited class method invocation', function()
|
||||
B:bar()
|
||||
end)
|
13
performance/time.lua
Normal file
13
performance/time.lua
Normal file
@ -0,0 +1,13 @@
|
||||
return function(title, f)
|
||||
|
||||
collectgarbage()
|
||||
|
||||
local startTime = os.clock()
|
||||
|
||||
for i=0,10000 do f() end
|
||||
|
||||
local endTime = os.clock()
|
||||
|
||||
print( title, endTime - startTime )
|
||||
|
||||
end
|
125
spec/Object_spec.lua
Normal file
125
spec/Object_spec.lua
Normal file
@ -0,0 +1,125 @@
|
||||
require 'middleclass'
|
||||
|
||||
context('Object', function()
|
||||
|
||||
|
||||
context('name', function()
|
||||
test('is correctly set', function()
|
||||
assert_equal(Object.name, 'Object')
|
||||
end)
|
||||
end)
|
||||
|
||||
context('tostring', function()
|
||||
test('returns "class Object"', function()
|
||||
assert_equal(tostring(Object), 'class Object')
|
||||
end)
|
||||
end)
|
||||
|
||||
context('()', function()
|
||||
test('returns an object, like Object:new()', function()
|
||||
local obj = Object()
|
||||
assert_true(instanceOf(Object, obj))
|
||||
end)
|
||||
end)
|
||||
|
||||
context('subclass', function()
|
||||
|
||||
test('throws an error when used without the :', function()
|
||||
assert_error(function() Object.subclass() end)
|
||||
end)
|
||||
|
||||
test('throws an error when no name is given', function()
|
||||
assert_error( function() Object:subclass() end)
|
||||
end)
|
||||
|
||||
context('when given a class name', function()
|
||||
|
||||
local SubClass
|
||||
|
||||
before(function()
|
||||
SubClass = Object:subclass('SubClass')
|
||||
end)
|
||||
|
||||
test('it returns a class with the correct name', function()
|
||||
assert_equal(SubClass.name, 'SubClass')
|
||||
end)
|
||||
|
||||
test('it returns a class with the correct superclass', function()
|
||||
assert_equal(SubClass.super, Object)
|
||||
end)
|
||||
|
||||
test('it includes the subclass in the list of subclasses', function()
|
||||
assert_true(Object.subclasses[SubClass])
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
context('instance creation', function()
|
||||
|
||||
local SubClass
|
||||
|
||||
before(function()
|
||||
SubClass = class('SubClass')
|
||||
function SubClass:initialize() self.mark=true end
|
||||
end)
|
||||
|
||||
context('allocate', function()
|
||||
|
||||
test('allocates instances properly', function()
|
||||
local instance = SubClass:allocate()
|
||||
assert_equal(instance.class, SubClass)
|
||||
assert_equal(tostring(instance), "instance of " .. tostring(SubClass))
|
||||
end)
|
||||
|
||||
test('throws an error when used without the :', function()
|
||||
assert_error(Object.allocate)
|
||||
end)
|
||||
|
||||
test('does not call the initializer', function()
|
||||
local allocated = SubClass:allocate()
|
||||
assert_nil(allocated.mark)
|
||||
end)
|
||||
|
||||
test('can be overriden', function()
|
||||
|
||||
local previousAllocate = SubClass.static.allocate
|
||||
|
||||
function SubClass.static:allocate()
|
||||
local instance = previousAllocate(SubClass)
|
||||
instance.mark = true
|
||||
return instance
|
||||
end
|
||||
|
||||
local allocated = SubClass:allocate()
|
||||
assert_true(allocated.mark)
|
||||
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
context('new', function()
|
||||
|
||||
test('initializes instances properly', function()
|
||||
local instance = SubClass:new()
|
||||
assert_equal(instance.class, SubClass)
|
||||
end)
|
||||
|
||||
test('throws an error when used without the :', function()
|
||||
assert_error(SubClass.new)
|
||||
end)
|
||||
|
||||
test('calls the initializer', function()
|
||||
local initialized = SubClass:new()
|
||||
assert_true(initialized.mark)
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
|
40
spec/class_spec.lua
Normal file
40
spec/class_spec.lua
Normal file
@ -0,0 +1,40 @@
|
||||
require 'middleclass'
|
||||
|
||||
context('class()', function()
|
||||
|
||||
context('when given no params', function()
|
||||
test('it throws an error', function()
|
||||
assert_error(class)
|
||||
end)
|
||||
end)
|
||||
|
||||
context('when given a name', function()
|
||||
local TheClass
|
||||
|
||||
before(function()
|
||||
TheClass = class('TheClass')
|
||||
end)
|
||||
|
||||
test('the resulting class has the correct name', function()
|
||||
assert_equal(TheClass.name, 'TheClass')
|
||||
end)
|
||||
|
||||
test('the resulting class has Object as its superclass', function()
|
||||
assert_equal(TheClass.super, Object)
|
||||
end)
|
||||
end)
|
||||
|
||||
context('when given a name and a superclass', function()
|
||||
local TheSuperClass = class('TheSuperClass')
|
||||
local TheSubClass = class('TheSubClass', TheSuperClass)
|
||||
|
||||
test('the resulting class has the correct name', function()
|
||||
assert_equal(TheSubClass.name, 'TheSubClass')
|
||||
end)
|
||||
|
||||
test('the resulting class has the correct superclass', function()
|
||||
assert_equal(TheSubClass.super, TheSuperClass)
|
||||
end)
|
||||
end)
|
||||
|
||||
end)
|
138
spec/classes_spec.lua
Normal file
138
spec/classes_spec.lua
Normal file
@ -0,0 +1,138 @@
|
||||
require 'middleclass'
|
||||
|
||||
context('A Class', function()
|
||||
|
||||
context('Default stuff', function()
|
||||
|
||||
local AClass
|
||||
|
||||
before(function()
|
||||
AClass = class('AClass')
|
||||
end)
|
||||
|
||||
context('name', function()
|
||||
test('is correctly set', function()
|
||||
assert_equal(AClass.name, 'AClass')
|
||||
end)
|
||||
end)
|
||||
|
||||
context('tostring', function()
|
||||
test('returns "class *name*"', function()
|
||||
assert_equal(tostring(AClass), 'class AClass')
|
||||
end)
|
||||
end)
|
||||
|
||||
context('()', function()
|
||||
test('returns an object, like Class:new()', function()
|
||||
local obj = AClass()
|
||||
assert_equal(obj.class, AClass)
|
||||
end)
|
||||
end)
|
||||
|
||||
context('include', function()
|
||||
test('throws an error when used without the :', function()
|
||||
assert_error(function() AClass.include() end)
|
||||
end)
|
||||
test('throws an error when passed a non-table:', function()
|
||||
assert_error(function() AClass:include(1) end)
|
||||
end)
|
||||
end)
|
||||
|
||||
context('subclass', function()
|
||||
|
||||
test('throws an error when used without the :', function()
|
||||
assert_error(function() AClass.subclass() end)
|
||||
end)
|
||||
|
||||
test('throws an error when no name is given', function()
|
||||
assert_error( function() AClass:subclass() end)
|
||||
end)
|
||||
|
||||
context('when given a subclass name', function()
|
||||
|
||||
local SubClass
|
||||
|
||||
before(function()
|
||||
function AClass.static:subclassed(other) self.static.child = other end
|
||||
SubClass = AClass:subclass('SubClass')
|
||||
end)
|
||||
|
||||
test('it returns a class with the correct name', function()
|
||||
assert_equal(SubClass.name, 'SubClass')
|
||||
end)
|
||||
|
||||
test('it returns a class with the correct superclass', function()
|
||||
assert_equal(SubClass.super, AClass)
|
||||
end)
|
||||
|
||||
test('it invokes the subclassed hook method', function()
|
||||
assert_equal(SubClass, AClass.child)
|
||||
end)
|
||||
|
||||
test('it includes the subclass in the list of subclasses', function()
|
||||
assert_true(AClass.subclasses[SubClass])
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
|
||||
|
||||
context('attributes', function()
|
||||
|
||||
local A, B
|
||||
|
||||
before(function()
|
||||
A = class('A')
|
||||
A.static.foo = 'foo'
|
||||
|
||||
B = class('B', A)
|
||||
end)
|
||||
|
||||
test('are available after being initialized', function()
|
||||
assert_equal(A.foo, 'foo')
|
||||
end)
|
||||
|
||||
test('are available for subclasses', function()
|
||||
assert_equal(B.foo, 'foo')
|
||||
end)
|
||||
|
||||
test('are overridable by subclasses, without affecting the superclasses', function()
|
||||
B.static.foo = 'chunky bacon'
|
||||
assert_equal(B.foo, 'chunky bacon')
|
||||
assert_equal(A.foo, 'foo')
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
context('methods', function()
|
||||
|
||||
local A, B
|
||||
|
||||
before(function()
|
||||
A = class('A')
|
||||
function A.static:foo() return 'foo' end
|
||||
|
||||
B = class('B', A)
|
||||
end)
|
||||
|
||||
test('are available after being initialized', function()
|
||||
assert_equal(A:foo(), 'foo')
|
||||
end)
|
||||
|
||||
test('are available for subclasses', function()
|
||||
assert_equal(B:foo(), 'foo')
|
||||
end)
|
||||
|
||||
test('are overridable by subclasses, without affecting the superclasses', function()
|
||||
function B.static:foo() return 'chunky bacon' end
|
||||
assert_equal(B:foo(), 'chunky bacon')
|
||||
assert_equal(A:foo(), 'foo')
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end)
|
66
spec/includes_spec.lua
Normal file
66
spec/includes_spec.lua
Normal file
@ -0,0 +1,66 @@
|
||||
require 'middleclass'
|
||||
|
||||
context('includes', function()
|
||||
|
||||
context('nils, numbers, etc', function()
|
||||
local o = Object:new()
|
||||
local primitives = {nil, 1, 'hello', {}, function() end}
|
||||
|
||||
for _,primitive in pairs(primitives) do
|
||||
local theType = type(primitive)
|
||||
context('A ' .. theType, function()
|
||||
|
||||
local f1 = function() return includes(Object, primitive) end
|
||||
local f2 = function() return includes(primitive, o) end
|
||||
local f3 = function() return includes(primitive, primitive) end
|
||||
|
||||
context('don\'t throw errors', function()
|
||||
test('includes(Object, '.. theType ..')', function()
|
||||
assert_not_error(f1)
|
||||
end)
|
||||
test('includes(' .. theType .. ', Object:new())', function()
|
||||
assert_not_error(f2)
|
||||
end)
|
||||
test('includes(' .. theType .. ',' .. theType ..')', function()
|
||||
assert_not_error(f3)
|
||||
end)
|
||||
end)
|
||||
|
||||
test('make includes return false', function()
|
||||
assert_false(f1())
|
||||
assert_false(f2())
|
||||
assert_false(f3())
|
||||
end)
|
||||
|
||||
end)
|
||||
end -- for
|
||||
|
||||
end)
|
||||
|
||||
context('A class', function()
|
||||
|
||||
local Class1 = class('Class1')
|
||||
local Class2 = class('Class2', Class1)
|
||||
local Class3 = class('Class3', Class2)
|
||||
local UnrelatedClass = class('Unrelated')
|
||||
|
||||
local hasFoo = { foo=function() return 'foo' end }
|
||||
Class1:include(hasFoo)
|
||||
|
||||
test('returns true if it includes a mixin', function()
|
||||
assert_true(includes(hasFoo, Class1))
|
||||
end)
|
||||
|
||||
test('returns true if its superclass includes a mixin', function()
|
||||
assert_true(includes(hasFoo, Class2))
|
||||
assert_true(includes(hasFoo, Class3))
|
||||
end)
|
||||
|
||||
test('returns false otherwise', function()
|
||||
assert_false(includes(hasFoo, UnrelatedClass))
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end)
|
||||
|
80
spec/instanceOf_spec.lua
Normal file
80
spec/instanceOf_spec.lua
Normal file
@ -0,0 +1,80 @@
|
||||
require 'middleclass'
|
||||
|
||||
context('instanceOf', function()
|
||||
|
||||
context('nils, integers, strings, tables, and functions', function()
|
||||
local o = Object:new()
|
||||
local primitives = {nil, 1, 'hello', {}, function() end}
|
||||
|
||||
for _,primitive in pairs(primitives) do
|
||||
local theType = type(primitive)
|
||||
context('A ' .. theType, function()
|
||||
|
||||
local f1 = function() return instanceOf(Object, primitive) end
|
||||
local f2 = function() return instanceOf(primitive, o) end
|
||||
local f3 = function() return instanceOf(primitive, primitive) end
|
||||
|
||||
context('does not throw errors', function()
|
||||
test('instanceOf(Object, '.. theType ..')', function()
|
||||
assert_not_error(f1)
|
||||
end)
|
||||
test('instanceOf(' .. theType .. ', Object:new())', function()
|
||||
assert_not_error(f2)
|
||||
end)
|
||||
test('instanceOf(' .. theType .. ',' .. theType ..')', function()
|
||||
assert_not_error(f3)
|
||||
end)
|
||||
end)
|
||||
|
||||
test('makes instanceOf return false', function()
|
||||
assert_false(f1())
|
||||
assert_false(f2())
|
||||
assert_false(f3())
|
||||
end)
|
||||
|
||||
end)
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
context('An instance', function()
|
||||
local Class1 = class('Class1')
|
||||
local Class2 = class('Class2', Class1)
|
||||
local Class3 = class('Class3', Class2)
|
||||
local UnrelatedClass = class('Unrelated')
|
||||
|
||||
local o1, o2, o3 = Class1:new(), Class2:new(), Class3:new()
|
||||
|
||||
test('is instanceOf(Object)', function()
|
||||
assert_true(instanceOf(Object, o1))
|
||||
assert_true(instanceOf(Object, o2))
|
||||
assert_true(instanceOf(Object, o3))
|
||||
end)
|
||||
|
||||
test('is instanceOf its class', function()
|
||||
assert_true(instanceOf(Class1, o1))
|
||||
assert_true(instanceOf(Class2, o2))
|
||||
assert_true(instanceOf(Class3, o3))
|
||||
end)
|
||||
|
||||
test('is instanceOf its class\' superclasses', function()
|
||||
assert_true(instanceOf(Class1, o2))
|
||||
assert_true(instanceOf(Class1, o3))
|
||||
assert_true(instanceOf(Class2, o3))
|
||||
end)
|
||||
|
||||
test('is not instanceOf its class\' subclasses', function()
|
||||
assert_false(instanceOf(Class2, o1))
|
||||
assert_false(instanceOf(Class3, o1))
|
||||
assert_false(instanceOf(Class3, o2))
|
||||
end)
|
||||
|
||||
test('is not instanceOf an unrelated class', function()
|
||||
assert_false(instanceOf(UnrelatedClass, o1))
|
||||
assert_false(instanceOf(UnrelatedClass, o2))
|
||||
assert_false(instanceOf(UnrelatedClass, o3))
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end)
|
65
spec/instances_spec.lua
Normal file
65
spec/instances_spec.lua
Normal file
@ -0,0 +1,65 @@
|
||||
require 'middleclass'
|
||||
|
||||
context('An instance', function()
|
||||
|
||||
context('attributes', function()
|
||||
|
||||
local Person
|
||||
|
||||
before(function()
|
||||
Person = class('Person')
|
||||
function Person:initialize(name)
|
||||
self.name = name
|
||||
end
|
||||
end)
|
||||
|
||||
test('are available in the instance after being initialized', function()
|
||||
local bob = Person:new('bob')
|
||||
assert_equal(bob.name, 'bob')
|
||||
end)
|
||||
|
||||
test('are available in the instance after being initialized by a superclass', function()
|
||||
local AgedPerson = class('AgedPerson', Person)
|
||||
function AgedPerson:initialize(name, age)
|
||||
Person.initialize(self, name)
|
||||
self.age = age
|
||||
end
|
||||
|
||||
local pete = AgedPerson:new('pete', 31)
|
||||
assert_equal(pete.name, 'pete')
|
||||
assert_equal(pete.age, 31)
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
context('methods', function()
|
||||
|
||||
local A, B, a, b
|
||||
|
||||
before(function()
|
||||
A = class('A')
|
||||
function A:overridden() return 'foo' end
|
||||
function A:regular() return 'regular' end
|
||||
|
||||
B = class('B', A)
|
||||
function B:overridden() return 'bar' end
|
||||
|
||||
a = A:new()
|
||||
b = B:new()
|
||||
end)
|
||||
|
||||
test('are available for any instance', function()
|
||||
assert_equal(a:overridden(), 'foo')
|
||||
end)
|
||||
|
||||
test('are inheritable', function()
|
||||
assert_equal(b:regular(), 'regular')
|
||||
end)
|
||||
|
||||
test('are overridable', function()
|
||||
assert_equal(b:overridden(), 'bar')
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end)
|
106
spec/metamethods_spec.lua
Normal file
106
spec/metamethods_spec.lua
Normal file
@ -0,0 +1,106 @@
|
||||
require 'middleclass'
|
||||
|
||||
context('Metamethods', function()
|
||||
|
||||
context('Custom Metamethods', function()
|
||||
-- Tests all metamethods. Note that __len is missing (lua makes table length unoverridable)
|
||||
-- I'll use a() to note the length of vector "a" (I would have preferred to use #a, but it's not possible)
|
||||
-- I'll be using 'a' instead of 'self' on this example since it is shorter
|
||||
local Vector= class('Vector')
|
||||
function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,z end
|
||||
function Vector.__tostring(a) return a.class.name .. '[' .. a.x .. ',' .. a.y .. ',' .. a.z .. ']' end
|
||||
function Vector.__eq(a,b) return a.x==b.x and a.y==b.y and a.z==b.z end
|
||||
function Vector.__lt(a,b) return a() < b() end
|
||||
function Vector.__le(a,b) return a() <= b() end
|
||||
function Vector.__add(a,b) return Vector:new(a.x+b.x, a.y+b.y ,a.z+b.z) end
|
||||
function Vector.__sub(a,b) return Vector:new(a.x-b.x, a.y-b.y, a.z-b.z) end
|
||||
function Vector.__div(a,s) return Vector:new(a.x/s, a.y/s, a.z/s) end
|
||||
function Vector.__unm(a) return Vector:new(-a.x, -a.y, -a.z) end
|
||||
function Vector.__concat(a,b) return a.x*b.x+a.y*b.y+a.z*b.z end
|
||||
function Vector.__call(a) return math.sqrt(a.x*a.x+a.y*a.y+a.z*a.z) end
|
||||
function Vector.__pow(a,b)
|
||||
return Vector:new(a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x)
|
||||
end
|
||||
function Vector.__mul(a,b)
|
||||
if type(b)=="number" then return Vector:new(a.x*b, a.y*b, a.z*b) end
|
||||
if type(a)=="number" then return Vector:new(a*b.x, a*b.y, a*b.z) end
|
||||
end
|
||||
|
||||
local a = Vector:new(1,2,3)
|
||||
local b = Vector:new(2,4,6)
|
||||
|
||||
for metamethod,values in pairs({
|
||||
__tostring = { tostring(a), "Vector[1,2,3]" },
|
||||
__eq = { a, a},
|
||||
__lt = { a<b, true },
|
||||
__le = { a<=b, true },
|
||||
__add = { a+b, Vector(3,6,9) },
|
||||
__sub = { b-a, Vector(1,2,3) },
|
||||
__div = { b/2, Vector(1,2,3) },
|
||||
__unm = { -a, Vector(-1,-2,-3) },
|
||||
__concat = { a..b, 28 },
|
||||
__call = { a(), math.sqrt(14) },
|
||||
__pow = { a^b, Vector(0,0,0) },
|
||||
__mul = { 4*a, Vector(4,8,12) }--,
|
||||
--__index = { b[1], 3 }
|
||||
}) do
|
||||
test(metamethod .. ' works as expected', function()
|
||||
assert_equal(values[1], values[2])
|
||||
end)
|
||||
end
|
||||
|
||||
context('Inherited Metamethods', function()
|
||||
local Vector2= class('Vector2', Vector)
|
||||
function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
|
||||
|
||||
local c = Vector2:new(1,2,3)
|
||||
local d = Vector2:new(2,4,6)
|
||||
for metamethod,values in pairs({
|
||||
__tostring = { tostring(c), "Vector2[1,2,3]" },
|
||||
__eq = { c, c },
|
||||
__lt = { c<d, true },
|
||||
__le = { c<=d, true },
|
||||
__add = { c+d, Vector(3,6,9) },
|
||||
__sub = { d-c, Vector(1,2,3) },
|
||||
__div = { d/2, Vector(1,2,3) },
|
||||
__unm = { -c, Vector(-1,-2,-3) },
|
||||
__concat = { c..d, 28 },
|
||||
__call = { c(), math.sqrt(14) },
|
||||
__pow = { c^d, Vector(0,0,0) },
|
||||
__mul = { 4*c, Vector(4,8,12) }
|
||||
}) do
|
||||
test(metamethod .. ' works as expected', function()
|
||||
assert_equal(values[1], values[2])
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
context('Default Metamethods', function()
|
||||
|
||||
local Peter, peter
|
||||
|
||||
before(function()
|
||||
Peter = class('Peter')
|
||||
peter = Peter()
|
||||
end)
|
||||
|
||||
context('A Class', function()
|
||||
test('has a call metamethod properly set', function()
|
||||
assert_true(instanceOf(Peter, peter))
|
||||
end)
|
||||
test('has a tostring metamethod properly set', function()
|
||||
assert_equal(tostring(Peter), 'class Peter')
|
||||
end)
|
||||
end)
|
||||
|
||||
context('An instance', function()
|
||||
test('has a tostring metamethod, returning a different result from Object.__tostring', function()
|
||||
assert_not_equal(Peter.__tostring, Object.__tostring)
|
||||
assert_equal(tostring(peter), 'instance of class Peter')
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
end)
|
53
spec/mixins_spec.lua
Normal file
53
spec/mixins_spec.lua
Normal file
@ -0,0 +1,53 @@
|
||||
require 'middleclass'
|
||||
|
||||
context('A Mixin', function()
|
||||
|
||||
local Mixin1, Mixin2, Class1, Class2
|
||||
|
||||
before(function()
|
||||
Mixin1, Mixin2 = {},{}
|
||||
|
||||
function Mixin1:included(theClass) theClass.includesMixin1 = true end
|
||||
function Mixin1:foo() return 'foo' end
|
||||
function Mixin1:bar() return 'bar' end
|
||||
Mixin1.static = {}
|
||||
Mixin1.static.bazzz = function() return 'bazzz' end
|
||||
|
||||
|
||||
function Mixin2:baz() return 'baz' end
|
||||
|
||||
Class1 = class('Class1'):include(Mixin1, Mixin2)
|
||||
function Class1:foo() return 'foo1' end
|
||||
|
||||
Class2 = class('Class2', Class1)
|
||||
function Class2:bar2() return 'bar2' end
|
||||
end)
|
||||
|
||||
test('invokes the "included" method when included', function()
|
||||
assert_true(Class1.includesMixin1)
|
||||
end)
|
||||
|
||||
test('has all its functions (except "included") copied to its target class', function()
|
||||
assert_equal(Class1:bar(), 'bar')
|
||||
assert_nil(Class1.included)
|
||||
end)
|
||||
|
||||
test('makes its functions available to subclasses', function()
|
||||
assert_equal(Class2:baz(), 'baz')
|
||||
end)
|
||||
|
||||
test('allows overriding of methods in the same class', function()
|
||||
assert_equal(Class2:foo(), 'foo1')
|
||||
end)
|
||||
|
||||
test('allows overriding of methods on subclasses', function()
|
||||
assert_equal(Class2:bar2(), 'bar2')
|
||||
end)
|
||||
|
||||
test('makes new static methods available in classes', function()
|
||||
assert_equal(Class1:bazzz(), 'bazzz')
|
||||
assert_equal(Class2:bazzz(), 'bazzz')
|
||||
end)
|
||||
|
||||
end)
|
||||
|
74
spec/subclassOf_spec.lua
Normal file
74
spec/subclassOf_spec.lua
Normal file
@ -0,0 +1,74 @@
|
||||
require 'middleclass'
|
||||
|
||||
context('subclassOf', function()
|
||||
|
||||
context('nils, integers, etc', function()
|
||||
local primitives = {nil, 1, 'hello', {}, function() end}
|
||||
|
||||
for _,primitive in pairs(primitives) do
|
||||
local theType = type(primitive)
|
||||
context('A ' .. theType, function()
|
||||
|
||||
local f1 = function() return subclassOf(Object, primitive) end
|
||||
local f2 = function() return subclassOf(primitive, o) end
|
||||
local f3 = function() return subclassOf(primitive, primitive) end
|
||||
|
||||
context('does not throw errors', function()
|
||||
test('subclassOf(Object, '.. theType ..')', function()
|
||||
assert_not_error(f1)
|
||||
end)
|
||||
test('subclassOf(' .. theType .. ', Object:new())', function()
|
||||
assert_not_error(f2)
|
||||
end)
|
||||
test('subclassOf(' .. theType .. ',' .. theType ..')', function()
|
||||
assert_not_error(f3)
|
||||
end)
|
||||
end)
|
||||
|
||||
test('makes subclassOf return false', function()
|
||||
assert_false(f1())
|
||||
assert_false(f2())
|
||||
assert_false(f3())
|
||||
end)
|
||||
|
||||
end)
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
context('Any class (except Object)', function()
|
||||
local Class1 = class('Class1')
|
||||
local Class2 = class('Class2', Class1)
|
||||
local Class3 = class('Class3', Class2)
|
||||
local UnrelatedClass = class('Unrelated')
|
||||
|
||||
test('is subclassOf(Object)', function()
|
||||
assert_true(subclassOf(Object, Class1))
|
||||
assert_true(subclassOf(Object, Class2))
|
||||
assert_true(subclassOf(Object, Class3))
|
||||
end)
|
||||
|
||||
test('is subclassOf its direct superclass', function()
|
||||
assert_true(subclassOf(Class1, Class2))
|
||||
assert_true(subclassOf(Class2, Class3))
|
||||
end)
|
||||
|
||||
test('is subclassOf its ancestors', function()
|
||||
assert_true(subclassOf(Class1, Class3))
|
||||
end)
|
||||
|
||||
test('is a subclassOf its class\' subclasses', function()
|
||||
assert_false(subclassOf(Class2, Class1))
|
||||
assert_false(subclassOf(Class3, Class1))
|
||||
assert_false(subclassOf(Class3, Class2))
|
||||
end)
|
||||
|
||||
test('is not a subclassOf an unrelated class', function()
|
||||
assert_false(subclassOf(UnrelatedClass, Class1))
|
||||
assert_false(subclassOf(UnrelatedClass, Class2))
|
||||
assert_false(subclassOf(UnrelatedClass, Class3))
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
end)
|
Loading…
Reference in New Issue
Block a user