Squashed 'src/lib/middleclass/' content from commit 27a64f1

git-subtree-dir: src/lib/middleclass
git-subtree-split: 27a64f107e61532006030a6168e4fe6a71eee9a2
This commit is contained in:
Paul Liverman III 2017-10-10 00:17:15 -07:00
commit 8ea4b63878
21 changed files with 1602 additions and 0 deletions

36
.travis.yml Normal file
View File

@ -0,0 +1,36 @@
language: python
sudo: false
env:
- LUA="lua=5.1"
- LUA="lua=5.2"
- LUA="lua=5.3"
- LUA="luajit=2.0"
- LUA="luajit=2.1"
before_install:
- pip install hererocks
- hererocks lua_install -r^ --$LUA
- export PATH=$PATH:$PWD/lua_install/bin # Add directory with all installed binaries to PATH
install:
- luarocks install luacheck
- luarocks install busted
- luarocks install luacov
- luarocks install luacov-coveralls
script:
- luacheck --no-unused-args --std max+busted *.lua spec
- busted --verbose --coverage
after_success:
- luacov-coveralls --exclude $TRAVIS_BUILD_DIR/lua_install
branches:
except:
- gh-pages
notifications:
email:
on_success: change
on_failure: always

48
CHANGELOG.md Normal file
View File

@ -0,0 +1,48 @@
middleclass changelog
====================
Version 4.1.0
* Simplifies implementation of `isInstanceOf` and `isSubclassOf`. They will now raise an error if their first
parameter (the `self`) isn't an instance or a class respectively.
Version 4.0.0
* Unified the method and metamethod lookup into a single algorithm
* Added the capacity of setting up the `__index` metamethod in classes
* Removed global `Object` (classes created with `class(<name>)` have no superclass now)
* Removed default method `Class:implements(<mixin>)`
* Renamed several internal functions
Version 3.2.0
* Changed the way metamethods were handled to fix certain bugs (un-stubbed metamethods could not be inherited)
Version 3.1.0
* Added Lua 5.3 metamethod support (`__band`, `__bor`, `__bxor`, `__shl`, `__bnot`)
Version 3.0.1
* Added `__len`, `__ipairs` and `__pairs` metamethods for Lua 5.2
Version 3.0
* Anything that behaves reasonably like a class can be a class (no internal list of classes)
* The `class` global function is now just the return value of `require
'middleclass'`. It is a callable table, but works exactly as before.
* The global variable `Object` becomes `class.Object`
* The global function `instanceOf` becomes `class.Object.isInstanceOf`. Parameter order is reversed.
* The global function `subclassOf` becomes `class.Object.static.isSubclassOf`. Parameter order is reversed.
* The global function `implements` becomes `class.Object.static.implements`. Parameter order is reversed.
* Specs have been translated from telescope to busted
Version 2.0
* Static methods are now separated from instance methods
* class.superclass has now become class.super
* It's now possible to do class.subclasses
* middleclass is now a single file; init.lua has dissapeared
* license is changed from BSD to MIT. License included in source FTW

20
MIT-LICENSE.txt Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2011 Enrique García Cota
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.

80
README.md Normal file
View File

@ -0,0 +1,80 @@
middleclass
===========
[![Build Status](https://travis-ci.org/kikito/middleclass.png?branch=master)](https://travis-ci.org/kikito/middleclass)
[![Coverage Status](https://coveralls.io/repos/kikito/middleclass/badge.svg?branch=master&service=github)](https://coveralls.io/github/kikito/middleclass?branch=master)
A simple OOP library for Lua. It has inheritance, metamethods (operators), class variables and weak mixin support.
Quick Look
==========
```lua
local class = require 'middleclass'
local Fruit = class('Fruit') -- 'Fruit' is the class' name
function Fruit:initialize(sweetness)
self.sweetness = sweetness
end
Fruit.static.sweetness_threshold = 5 -- class variable (also admits methods)
function Fruit:isSweet()
return self.sweetness > Fruit.sweetness_threshold
end
local Lemon = class('Lemon', Fruit) -- subclassing
function Lemon:initialize()
Fruit.initialize(self, 1) -- invoking the superclass' initializer
end
local lemon = Lemon:new()
print(lemon:isSweet()) -- false
```
Documentation
=============
See the [github wiki page](https://github.com/kikito/middleclass/wiki) for examples & documentation.
You can read the `CHANGELOG.md` file to see what has changed on each version of this library.
If you need help updating to a new middleclass version, read `UPDATING.md`.
Installation
============
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:
```lua
local class = require 'middleclass'
```
Specs
=====
This project uses [busted](http://olivinelabs.com/busted/) for its specs. If you want to run the specs, you will have to install it first. Then just execute the following:
```bash
cd /folder/where/the/spec/folder/is
busted
```
Performance tests
=================
Middleclass also comes with a small performance test suite. Just run the following command:
```bash
lua performance/run.lua
```
License
=======
Middleclass is distributed under the MIT license.

69
UPDATING.md Normal file
View File

@ -0,0 +1,69 @@
Updating from 3.x to 4.x
========================
In middleclass 4.0 there is no global `Object` class any more. Classes created with `class(<name>)` don't have a superclass any more.
If you need a global `Object` class, you must create it explicitly and then use it when creating new classes:
```lua
local Object = class('Object')
...
local MyClass = class('MyClass', Object)
```
If you are using a library which depends on the internal implementation of middleclass they might not work with middleclass 4.0. You might need to update those other libraries.
Middleclass 4.0 comes with support for `__index` metamethod support. If your library manipulated the classes' `__instanceDict` internal attribute, you might do the same thing now using `__index` instead.
Also note that the class method `:implements` has been removed.
Updating from 2.x to 3.x
========================
Middleclass used to expose several global variables on the main scope. It does not do that anymore.
`class` is now returned by `require 'middleclass'`, and it is not set globally. So you can do this:
```lua
local class = require 'middleclass'
local MyClass = class('MyClass') -- works as before
```
`Object` is not a global variable any more. But you can get it from `class.Object`
```lua
local class = require 'middleclass'
local Object = class.Object
print(Object) -- prints 'class Object'
```
The public functions `instanceOf`, `subclassOf` and `includes` have been replaced by `Object.isInstanceOf`, `Object.static.isSubclassOf` and `Object.static.includes`.
Prior to 3.x:
```lua
instanceOf(MyClass, obj)
subclassOf(Object, aClass)
includes(aMixin, aClass)
```
Since 3.x:
```lua
obj:isInstanceOf(MyClass)
aClass:isSubclassOf(Object)
aClass:includes(aMixin)
```
The 3.x code snippet will throw an error if `obj` is not an object, or if `aClass` is not a class (since they will not implement `isInstanceOf`, `isSubclassOf` or `includes`).
If you are unsure of whether `obj` and `aClass` are an object or a class, you can use the methods in `Object`. They are prepared to work with random types, not just classes and instances:
```lua
Object.isInstanceOf(obj, MyClass)
Object.isSubclassOf(aClass, Object)
Object.includes(aClass, aMixin)
```
Notice that the parameter order is not the same now as it was in 2.x. Also note the change in naming: `isInstanceOf` instead of `instanceOf`, and `isSubclassOf` instead of `subclassOf`.

170
middleclass.lua Normal file
View File

@ -0,0 +1,170 @@
local middleclass = {
_VERSION = 'middleclass v4.1.0',
_DESCRIPTION = 'Object Orientation for Lua',
_URL = 'https://github.com/kikito/middleclass',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2011 Enrique García Cota
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.
]]
}
local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
else
return function(self, name)
local value = aClass.__instanceDict[name]
if value ~= nil then
return value
elseif type(f) == "function" then
return (f(self, name))
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) return rawget(dict,k) or super.static[k] 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 (aClass == self.class or 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
_propagateInstanceMethod(subclass, methodName, f)
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

43
performance/run.lua Normal file
View File

@ -0,0 +1,43 @@
local class = 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
View 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

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "3.0-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v3.0.0.tar.gz",
dir = "middleclass-3.0.0"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "3.1-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v3.1.0.tar.gz",
dir = "middleclass-3.1.0"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "3.2-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v3.2.0.tar.gz",
dir = "middleclass-3.2.0"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "4.0-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v4.0.0.tar.gz",
dir = "middleclass-4.0.0"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "4.1-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v4.1.0.tar.gz",
dir = "middleclass-4.1.0"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

28
spec/class_spec.lua Normal file
View File

@ -0,0 +1,28 @@
local class = require 'middleclass'
describe('class()', function()
describe('when given no params', function()
it('it throws an error', function()
assert.error(class)
end)
end)
describe('when given a name', function()
it('the resulting class has the correct name and Object as its superclass', function()
local TheClass = class('TheClass')
assert.equal(TheClass.name, 'TheClass')
assert.is_nil(TheClass.super)
end)
end)
describe('when given a name and a superclass', function()
it('the resulting class has the correct name and superclass', function()
local TheSuperClass = class('TheSuperClass')
local TheSubClass = class('TheSubClass', TheSuperClass)
assert.equal(TheSubClass.name, 'TheSubClass')
assert.equal(TheSubClass.super, TheSuperClass)
end)
end)
end)

138
spec/classes_spec.lua Normal file
View File

@ -0,0 +1,138 @@
local class = require 'middleclass'
describe('A Class', function()
describe('Default stuff', function()
local AClass
before_each(function()
AClass = class('AClass')
end)
describe('name', function()
it('is correctly set', function()
assert.equal(AClass.name, 'AClass')
end)
end)
describe('tostring', function()
it('returns "class *name*"', function()
assert.equal(tostring(AClass), 'class AClass')
end)
end)
describe('()', function()
it('returns an object, like Class:new()', function()
local obj = AClass()
assert.equal(obj.class, AClass)
end)
end)
describe('include', function()
it('throws an error when used without the :', function()
assert.error(function() AClass.include() end)
end)
it('throws an error when passed a non-table:', function()
assert.error(function() AClass:include(1) end)
end)
end)
describe('subclass', function()
it('throws an error when used without the :', function()
assert.error(function() AClass.subclass() end)
end)
it('throws an error when no name is given', function()
assert.error( function() AClass:subclass() end)
end)
describe('when given a subclass name', function()
local SubClass
before_each(function()
function AClass.static:subclassed(other) self.static.child = other end
SubClass = AClass:subclass('SubClass')
end)
it('it returns a class with the correct name', function()
assert.equal(SubClass.name, 'SubClass')
end)
it('it returns a class with the correct superclass', function()
assert.equal(SubClass.super, AClass)
end)
it('it invokes the subclassed hook method', function()
assert.equal(SubClass, AClass.child)
end)
it('it includes the subclass in the list of subclasses', function()
assert.is_true(AClass.subclasses[SubClass])
end)
end)
end)
end)
describe('attributes', function()
local A, B
before_each(function()
A = class('A')
A.static.foo = 'foo'
B = class('B', A)
end)
it('are available after being initialized', function()
assert.equal(A.foo, 'foo')
end)
it('are available for subclasses', function()
assert.equal(B.foo, 'foo')
end)
it('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)
describe('methods', function()
local A, B
before_each(function()
A = class('A')
function A.static:foo() return 'foo' end
B = class('B', A)
end)
it('are available after being initialized', function()
assert.equal(A:foo(), 'foo')
end)
it('are available for subclasses', function()
assert.equal(B:foo(), 'foo')
end)
it('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)

View File

@ -0,0 +1,226 @@
local class = require 'middleclass'
describe('Default methods', function()
local Object
before_each(function()
Object = class('Object')
end)
describe('name', function()
it('is correctly set', function()
assert.equal(Object.name, 'Object')
end)
end)
describe('tostring', function()
it('returns "class Object"', function()
assert.equal(tostring(Object), 'class Object')
end)
end)
describe('()', function()
it('returns an object, like Object:new()', function()
local obj = Object()
assert.is_true(obj:isInstanceOf(Object))
end)
end)
describe('subclass', function()
it('throws an error when used without the :', function()
assert.error(function() Object.subclass() end)
end)
it('throws an error when no name is given', function()
assert.error( function() Object:subclass() end)
end)
describe('when given a class name', function()
local SubClass
before_each(function()
SubClass = Object:subclass('SubClass')
end)
it('it returns a class with the correct name', function()
assert.equal(SubClass.name, 'SubClass')
end)
it('it returns a class with the correct superclass', function()
assert.equal(SubClass.super, Object)
end)
it('it includes the subclass in the list of subclasses', function()
assert.is_true(Object.subclasses[SubClass])
end)
end)
end)
describe('instance creation', function()
local SubClass
before_each(function()
SubClass = class('SubClass')
function SubClass:initialize() self.mark=true end
end)
describe('allocate', function()
it('allocates instances properly', function()
local instance = SubClass:allocate()
assert.equal(instance.class, SubClass)
assert.equal(tostring(instance), "instance of " .. tostring(SubClass))
end)
it('throws an error when used without the :', function()
assert.error(Object.allocate)
end)
it('does not call the initializer', function()
local allocated = SubClass:allocate()
assert.is_nil(allocated.mark)
end)
it('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.is_true(allocated.mark)
end)
end)
describe('new', function()
it('initializes instances properly', function()
local instance = SubClass:new()
assert.equal(instance.class, SubClass)
end)
it('throws an error when used without the :', function()
assert.error(SubClass.new)
end)
it('calls the initializer', function()
local initialized = SubClass:new()
assert.is_true(initialized.mark)
end)
end)
describe('isInstanceOf', function()
describe('primitives', function()
local o = Object:new()
local primitives = {nil, 1, 'hello', {}, function() end, Object:new()}
for _,primitive in pairs(primitives) do
local theType = type(primitive)
describe('A ' .. theType, function()
it('object:isInstanceOf(, '.. theType ..') returns false', function()
assert.is_false(o:isInstanceOf(primitive))
end)
end)
end
end)
describe('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()
it('isInstanceOf its class', function()
assert.is_true(o1:isInstanceOf(Class1))
assert.is_true(o2:isInstanceOf(Class2))
assert.is_true(o3:isInstanceOf(Class3))
end)
it('is instanceOf its class\' superclasses', function()
assert.is_true(o2:isInstanceOf(Class1))
assert.is_true(o3:isInstanceOf(Class1))
assert.is_true(o3:isInstanceOf(Class2))
end)
it('is not instanceOf its class\' subclasses', function()
assert.is_false(o1:isInstanceOf(Class2))
assert.is_false(o1:isInstanceOf(Class3))
assert.is_false(o2:isInstanceOf(Class3))
end)
it('is not instanceOf an unrelated class', function()
assert.is_false(o1:isInstanceOf(UnrelatedClass))
assert.is_false(o2:isInstanceOf(UnrelatedClass))
assert.is_false(o3:isInstanceOf(UnrelatedClass))
end)
end)
end)
end)
describe('isSubclassOf', function()
it('returns false for instances', function()
assert.is_false(Object:isSubclassOf(Object:new()))
end)
describe('on primitives', function()
local primitives = {nil, 1, 'hello', {}, function() end}
for _,primitive in pairs(primitives) do
local theType = type(primitive)
it('returns false for ' .. theType, function()
assert.is_false(Object:isSubclassOf(primitive))
end)
end
end)
describe('Any class (except Object)', function()
local Class1 = class('Class1')
local Class2 = class('Class2', Class1)
local Class3 = class('Class3', Class2)
local UnrelatedClass = class('Unrelated')
it('is subclassOf its direct superclass', function()
assert.is_true(Class2:isSubclassOf(Class1))
assert.is_true(Class3:isSubclassOf(Class2))
end)
it('is subclassOf its ancestors', function()
assert.is_true(Class3:isSubclassOf(Class1))
end)
it('is a subclassOf its class\' subclasses', function()
assert.is_true(Class2:isSubclassOf(Class1))
assert.is_true(Class3:isSubclassOf(Class1))
assert.is_true(Class3:isSubclassOf(Class2))
end)
it('is not a subclassOf an unrelated class', function()
assert.is_false(Class1:isSubclassOf(UnrelatedClass))
assert.is_false(Class2:isSubclassOf(UnrelatedClass))
assert.is_false(Class3:isSubclassOf(UnrelatedClass))
end)
end)
end)
end)

65
spec/instances_spec.lua Normal file
View File

@ -0,0 +1,65 @@
local class = require 'middleclass'
describe('An instance', function()
describe('attributes', function()
local Person
before_each(function()
Person = class('Person')
function Person:initialize(name)
self.name = name
end
end)
it('are available in the instance after being initialized', function()
local bob = Person:new('bob')
assert.equal(bob.name, 'bob')
end)
it('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)
describe('methods', function()
local A, B, a, b
before_each(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)
it('are available for any instance', function()
assert.equal(a:overridden(), 'foo')
end)
it('are inheritable', function()
assert.equal(b:regular(), 'regular')
end)
it('are overridable', function()
assert.equal(b:overridden(), 'bar')
end)
end)
end)

View File

@ -0,0 +1,85 @@
local class = require 'middleclass'
local it = require('busted').it
local describe = require('busted').describe
local before_each = require('busted').before_each
local assert = require('busted').assert
describe('Lua 5.2 Metamethods', function()
local Vector, v
before_each(function()
Vector= class('Vector')
function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,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.__len(a) return 3 end
function Vector.__pairs(a)
local t = {x=a.x,y=a.y,z=a.z}
return coroutine.wrap(function()
for k,val in pairs(t) do
coroutine.yield(k,val)
end
end)
end
function Vector.__ipairs(a)
local t = {a.x,a.y,a.z}
return coroutine.wrap(function()
for k,val in ipairs(t) do
coroutine.yield(k,val)
end
end)
end
v = Vector:new(1,2,3)
end)
it('implements __len', function()
assert.equal(#v, 3)
end)
it('implements __pairs',function()
local output = {}
for k,val in pairs(v) do
output[k] = val
end
assert.are.same(output,{x=1,y=2,z=3})
end)
it('implements __ipairs',function()
local output = {}
for _,i in ipairs(v) do
output[#output+1] = i
end
assert.are.same(output,{1,2,3})
end)
describe('Inherited Metamethods', function()
local Vector2, v2
before_each(function()
Vector2= class('Vector2', Vector)
function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
v2 = Vector2:new(1,2,3)
end)
it('implements __len', function()
assert.equal(#v2, 3)
end)
it('implements __pairs',function()
local output = {}
for k,val in pairs(v2) do
output[k] = val
end
assert.are.same(output,{x=1,y=2,z=3})
end)
it('implements __ipairs',function()
local output = {}
for _,i in ipairs(v2) do
output[#output+1] = i
end
assert.are.same(output,{1,2,3})
end)
end)
end)

View File

@ -0,0 +1,106 @@
local class = require 'middleclass'
local it = require('busted').it
local describe = require('busted').describe
local before_each = require('busted').before_each
local assert = require('busted').assert
describe('Lua 5.3 Metamethods', function()
local Vector, v, last_gc
before_each(function()
Vector= class('Vector')
function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,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.__pairs(a)
local t = {x=a.x,y=a.y,z=a.z}
return coroutine.wrap(function()
for k,val in pairs(t) do
coroutine.yield(k,val)
end
end)
end
function Vector.__len(a) return 3 end
function Vector.__gc(a) last_gc = {a.class.name, a.x, a.y, a.z} end
function Vector.__band(a,n) return a.class:new(a.x & n, a.y & n, a.z & n) end
function Vector.__bor(a,n) return a.class:new(a.x | n, a.y | n, a.z | n) end
function Vector.__bxor(a,n) return a.class:new(a.x ~ n, a.y ~ n, a.z ~ n) end
function Vector.__shl(a,n) return a.class:new(a.x << n, a.y << n, a.z << n) end
function Vector.__shr(a,n) return a.class:new(a.x >> n, a.y >> n, a.z >> n) end
function Vector.__bnot(a) return a.class:new(~a.x, ~a.y, ~a.z) end
v = Vector:new(1,2,3)
end)
it('implements __gc', function()
collectgarbage()
v = nil
collectgarbage()
assert.are.same(last_gc, {"Vector",1,2,3})
end)
it('implements __band', function()
assert.equal(v & 1, Vector(1,0,1))
end)
it('implements __bor', function()
assert.equal(v | 0, Vector(1,2,3))
end)
it('implements __bxor', function()
assert.equal(v | 1, Vector(1,3,3))
end)
it('implements __shl', function()
assert.equal(v << 1, Vector(2,4,6))
end)
it('implements __shr', function()
assert.equal(v >> 1, Vector(0,1,1))
end)
it('implements __bnot', function()
assert.equal(~v, Vector(-2,-3,-4))
end)
describe('Inherited Metamethods', function()
local Vector2, v2
before_each(function()
Vector2= class('Vector2', Vector)
function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
v2 = Vector2:new(1,2,3)
end)
it('implements __gc', function()
collectgarbage()
v2 = nil
collectgarbage()
assert.are.same(last_gc, {"Vector2",1,2,3})
end)
it('implements __band', function()
assert.equal(v2 & 1, Vector2(1,0,1))
end)
it('implements __bor', function()
assert.equal(v2 | 0, Vector2(1,2,3))
end)
it('implements __bxor', function()
assert.equal(v2 | 1, Vector2(1,3,3))
end)
it('implements __shl', function()
assert.equal(v2 << 1, Vector2(2,4,6))
end)
it('implements __shr', function()
assert.equal(v2 >> 1, Vector2(0,1,1))
end)
it('implements __bnot', function()
assert.equal(~v2, Vector2(-2,-3,-4))
end)
end)
end)

317
spec/metamethods_spec.lua Normal file
View File

@ -0,0 +1,317 @@
local class = require 'middleclass'
local function is_lua_5_2_compatible()
return type(rawlen) == 'function'
end
local function is_lua_5_3_compatible()
return type(string.unpack) == 'function'
end
if is_lua_5_2_compatible() then
require 'spec/metamethods_lua_5_2'
end
if is_lua_5_3_compatible() then
require 'spec.metamethods_lua_5_3'
end
describe('Metamethods', function()
describe('Custom Metamethods', function()
local Vector, v, w
before_each(function()
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 a.class:new(a.x+b.x, a.y+b.y ,a.z+b.z) end
function Vector.__sub(a,b) return a.class:new(a.x-b.x, a.y-b.y, a.z-b.z) end
function Vector.__div(a,s) return a.class:new(a.x/s, a.y/s, a.z/s) end
function Vector.__unm(a) return a.class: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 a.class: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 a.class:new(a.x*b, a.y*b, a.z*b) end
if type(a)=="number" then return b.class:new(a*b.x, a*b.y, a*b.z) end
end
Vector.__metatable = "metatable of a vector"
Vector.__mode = "k"
v = Vector:new(1,2,3)
w = Vector:new(2,4,6)
end)
it('implements __tostring', function()
assert.equal(tostring(v), "Vector[1,2,3]")
end)
it('implements __eq', function()
assert.equal(v, v)
end)
it('implements __lt', function()
assert.is_true(v < w)
end)
it('implements __le', function()
assert.is_true(v <= w)
end)
it('implements __add', function()
assert.equal(v+w, Vector(3,6,9))
end)
it('implements __sub', function()
assert.equal(w-v, Vector(1,2,3))
end)
it('implements __div', function()
assert.equal(w/2, Vector(1,2,3))
end)
it('implements __concat', function()
assert.equal(v..w, 28)
end)
it('implements __call', function()
assert.equal(v(), math.sqrt(14))
end)
it('implements __pow', function()
assert.equal(v^w, Vector(0,0,0))
end)
it('implements __mul', function()
assert.equal(4*v, Vector(4,8,12))
end)
it('implements __metatable', function()
assert.equal("metatable of a vector", getmetatable(v))
end)
it('implements __mode', function()
v[{}] = true
collectgarbage()
for k in pairs(v) do assert.not_table(k) end
end)
--[[
it('implements __index', function()
assert.equal(b[1], 3)
end)
--]]
describe('Inherited Metamethods', function()
local Vector2, v2, w2
before_each(function()
Vector2= class('Vector2', Vector)
function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
v2 = Vector2:new(1,2,3)
w2 = Vector2:new(2,4,6)
end)
it('implements __tostring', function()
assert.equal(tostring(v2), "Vector2[1,2,3]")
end)
it('implements __eq', function()
assert.equal(v2, v2)
end)
it('implements __lt', function()
assert.is_true(v2 < w2)
end)
it('implements __le', function()
assert.is_true(v2 <= w2)
end)
it('implements __add', function()
assert.equal(v2+w2, Vector2(3,6,9))
end)
it('implements __sub', function()
assert.equal(w2-v2, Vector2(1,2,3))
end)
it('implements __div', function()
assert.equal(w2/2, Vector2(1,2,3))
end)
it('implements __concat', function()
assert.equal(v2..w2, 28)
end)
it('implements __call', function()
assert.equal(v2(), math.sqrt(14))
end)
it('implements __pow', function()
assert.equal(v2^w2, Vector2(0,0,0))
end)
it('implements __mul', function()
assert.equal(4*v2, Vector2(4,8,12))
end)
it('implements __metatable', function()
assert.equal("metatable of a vector", getmetatable(v2))
end)
it('implements __mode', function()
v2[{}] = true
collectgarbage()
for k in pairs(v2) do assert.not_table(k) end
end)
it('allows inheriting further', function()
local Vector3 = class('Vector3', Vector2)
local v3 = Vector3(1,2,3)
local w3 = Vector3(3,4,5)
assert.equal(v3+w3, Vector3(4,6,8))
end)
describe('Updates', function()
it('overrides __add', function()
Vector2.__add = function(a, b) return Vector.__add(a, b)/2 end
assert.equal(v2+w2, Vector2(1.5,3,4.5))
end)
it('updates __add', function()
Vector.__add = Vector.__sub
assert.equal(v2+w2, Vector2(-1,-2,-3))
end)
it('does not update __add after overriding', function()
Vector2.__add = function(a, b) return Vector.__add(a, b)/2 end
Vector.__add = Vector.__sub
assert.equal(v2+w2, Vector2(-0.5,-1,-1.5))
end)
it('reverts __add override', function()
Vector2.__add = function(a, b) return Vector.__add(a, b)/2 end
Vector2.__add = nil
assert.equal(v2+w2, Vector2(3,6,9))
end)
end)
end)
end)
describe('Custom __index and __newindex', function()
describe('Tables', function()
local Proxy, fallback, p
before_each(function()
Proxy = class('Proxy')
fallback = {foo = 'bar', common = 'fallback'}
Proxy.__index = fallback
Proxy.__newindex = fallback
Proxy.common = 'class'
p = Proxy()
end)
it('uses __index', function()
assert.equal(p.foo, 'bar')
end)
it('does not use __index when field exists in class', function()
assert.equal(p.common, 'class')
end)
it('uses __newindex', function()
p.key = 'value'
assert.equal(fallback.key, 'value')
end)
it('uses __newindex when field exists in class', function()
p.common = 'value'
assert.equal(p.common, 'class')
assert.equal(Proxy.common, 'class')
assert.equal(fallback.common, 'value')
end)
end)
describe('Functions', function()
local Namespace, Rectangle, r
before_each(function()
Namespace = class('Namespace')
function Namespace:__index(name)
local getter = self.class[name.."Getter"]
if getter then return getter(self) end
end
function Namespace:__newindex(name, value)
local setter = self.class[name.."Setter"]
if setter then setter(self, value) else rawset(self, name, value) end
end
Rectangle = class('Rectangle', Namespace)
function Rectangle:initialize(x, y, scale)
self._scale, self.x, self.y = 1, x, y
self.scale = scale
end
function Rectangle:scaleGetter() return self._scale end
function Rectangle:scaleSetter(v)
self.x = self.x*v/self._scale
self.y = self.y*v/self._scale
self._scale = v
end
function Rectangle:areaGetter() return self.x * self.y end
r = Rectangle(3, 4, 2)
end)
it('uses setter', function()
assert.equal(r.x, 6)
assert.equal(r.y, 8)
r.scale = 3
assert.equal(r.x, 9)
assert.equal(r.y, 12)
end)
it('uses getters', function()
assert.equal(r.scale, 2)
assert.equal(r.area, 48)
end)
it('updates inherited __index', function()
function Namespace.__index() return 42 end
assert.equal(r.area, 42)
function Rectangle.__index() return 24 end
assert.equal(r.area, 24)
function Namespace.__index() return 96 end
assert.equal(r.area, 24)
Rectangle.__index = nil
assert.equal(r.area, 96)
end)
end)
end)
describe('Default Metamethods', function()
local Peter, peter
before_each(function()
Peter = class('Peter')
peter = Peter()
end)
describe('A Class', function()
it('has a call metamethod properly set', function()
assert.is_true(peter:isInstanceOf(Peter))
end)
it('has a tostring metamethod properly set', function()
assert.equal(tostring(Peter), 'class Peter')
end)
end)
describe('An instance', function()
it('has a tostring metamethod, returning a different result from Object.__tostring', function()
assert.equal(tostring(peter), 'instance of class Peter')
end)
end)
end)
end)

53
spec/mixins_spec.lua Normal file
View File

@ -0,0 +1,53 @@
local class = require 'middleclass'
describe('A Mixin', function()
local Mixin1, Mixin2, Class1, Class2
before_each(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)
it('invokes the "included" method when included', function()
assert.is_true(Class1.includesMixin1)
end)
it('has all its functions (except "included") copied to its target class', function()
assert.equal(Class1:bar(), 'bar')
assert.is_nil(Class1.included)
end)
it('makes its functions available to subclasses', function()
assert.equal(Class2:baz(), 'baz')
end)
it('allows overriding of methods in the same class', function()
assert.equal(Class2:foo(), 'foo1')
end)
it('allows overriding of methods on subclasses', function()
assert.equal(Class2:bar2(), 'bar2')
end)
it('makes new static methods available in classes', function()
assert.equal(Class1:bazzz(), 'bazzz')
assert.equal(Class2:bazzz(), 'bazzz')
end)
end)