• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lunarmodules / Penlight / 474

01 Feb 2024 02:24PM UTC coverage: 89.675% (+0.8%) from 88.873%
474

push

appveyor

web-flow
docs: Fix typos (#462)

7 of 7 new or added lines in 2 files covered. (100.0%)

140 existing lines in 18 files now uncovered.

5489 of 6121 relevant lines covered (89.67%)

345.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.35
/lua/pl/class.lua
1
--- Provides a reusable and convenient framework for creating classes in Lua.
2
-- Two possible notations:
3
--
4
--    B = class(A)
5
--    class.B(A)
6
--
7
-- The latter form creates a named class within the current environment. Note
8
-- that this implicitly brings in `pl.utils` as a dependency.
9
--
10
-- See the Guide for further @{01-introduction.md.Simplifying_Object_Oriented_Programming_in_Lua|discussion}
11
-- @module pl.class
12

13
local error, getmetatable, io, pairs, rawget, rawset, setmetatable, tostring, type =
14
    _G.error, _G.getmetatable, _G.io, _G.pairs, _G.rawget, _G.rawset, _G.setmetatable, _G.tostring, _G.type
184✔
15
local compat
16

17
-- this trickery is necessary to prevent the inheritance of 'super' and
18
-- the resulting recursive call problems.
19
local function call_ctor (c,obj,...)
20
    local init = rawget(c,'_init')
2,072✔
21
    local parent_with_init = rawget(c,'_parent_with_init')
2,072✔
22

23
    if parent_with_init then
2,072✔
24
        if not init then -- inheriting an init
536✔
25
            init = rawget(parent_with_init, '_init')
112✔
26
            parent_with_init = rawget(parent_with_init, '_parent_with_init')
112✔
27
        end
28
        if parent_with_init then -- super() points to one above wherever _init came from
536✔
29
            rawset(obj,'super',function(obj,...)
848✔
30
                call_ctor(parent_with_init,obj,...)
176✔
31
            end)
32
        end
33
    else
34
        -- Without this, calling super() where none exists will sometimes loop and stack overflow
35
        rawset(obj,'super',nil)
1,536✔
36
    end
37

38
    local res = init(obj,...)
2,072✔
39
    if parent_with_init then -- If this execution of call_ctor set a super, unset it
2,032✔
40
        rawset(obj,'super',nil)
408✔
41
    end
42

43
    return res
2,032✔
44
end
45

46
--- initializes an __instance__ upon creation.
47
-- @function class:_init
48
-- @param ... parameters passed to the constructor
49
-- @usage local Cat = class()
50
-- function Cat:_init(name)
51
--   --self:super(name)   -- call the ancestor initializer if needed
52
--   self.name = name
53
-- end
54
--
55
-- local pussycat = Cat("pussycat")
56
-- print(pussycat.name)  --> pussycat
57

58
--- checks whether an __instance__ is derived from some class.
59
-- Works the other way around as `class_of`. It has two ways of using;
60
-- 1) call with a class to check against, 2) call without params.
61
-- @function instance:is_a
62
-- @param some_class class to check against, or `nil` to return the class
63
-- @return `true` if `instance` is derived from `some_class`, or if `some_class == nil` then
64
-- it returns the class table of the instance
65
-- @usage local pussycat = Lion()  -- assuming Lion derives from Cat
66
-- if pussycat:is_a(Cat) then
67
--   -- it's true, it is a Lion, but also a Cat
68
-- end
69
--
70
-- if pussycat:is_a() == Lion then
71
--   -- It's true
72
-- end
73
local function is_a(self,klass)
74
    if klass == nil then
328✔
75
        -- no class provided, so return the class this instance is derived from
76
        return getmetatable(self)
8✔
77
    end
78
    local m = getmetatable(self)
320✔
79
    if not m then return false end --*can't be an object!
320✔
80
    while m do
376✔
81
        if m == klass then return true end
352✔
82
        m = rawget(m,'_base')
144✔
83
    end
84
    return false
24✔
85
end
86

87
--- checks whether an __instance__ is derived from some class.
88
-- Works the other way around as `is_a`.
89
-- @function some_class:class_of
90
-- @param some_instance instance to check against
91
-- @return `true` if `some_instance` is derived from `some_class`
92
-- @usage local pussycat = Lion()  -- assuming Lion derives from Cat
93
-- if Cat:class_of(pussycat) then
94
--   -- it's true
95
-- end
96
local function class_of(klass,obj)
97
    if type(klass) ~= 'table' or not rawget(klass,'is_a') then return false end
248✔
98
    return klass.is_a(obj,klass)
248✔
99
end
100

101
--- cast an object to another class.
102
-- It is not clever (or safe!) so use carefully.
103
-- @param some_instance the object to be changed
104
-- @function some_class:cast
105
local function cast (klass, obj)
106
    return setmetatable(obj,klass)
8✔
107
end
108

109

110
local function _class_tostring (obj)
UNCOV
111
    local mt = obj._class
×
112
    local name = rawget(mt,'_name')
×
113
    setmetatable(obj,nil)
×
114
    local str = tostring(obj)
×
115
    setmetatable(obj,mt)
×
116
    if name then str = name ..str:gsub('table','') end
×
117
    return str
×
118
end
119

120
local function tupdate(td,ts,dont_override)
121
    for k,v in pairs(ts) do
4,760✔
122
        if not dont_override or td[k] == nil then
4,496✔
123
            td[k] = v
4,472✔
124
        end
125
    end
126
end
127

128
local function _class(base,c_arg,c)
129
    -- the class `c` will be the metatable for all its objects,
130
    -- and they will look up their methods in it.
131
    local mt = {}   -- a metatable for the class to support __call and _handler
744✔
132
    -- can define class by passing it a plain table of methods
133
    local plain = type(base) == 'table' and not getmetatable(base)
744✔
134
    if plain then
744✔
135
        c = base
24✔
136
        base = c._base
24✔
137
    else
138
        c = c or {}
720✔
139
    end
140

141
    if type(base) == 'table' then
744✔
142
        -- our new class is a shallow copy of the base class!
143
        -- but be careful not to wipe out any methods we have been given at this point!
144
        tupdate(c,base,plain)
264✔
145
        c._base = base
264✔
146
        -- inherit the 'not found' handler, if present
147
        if rawget(c,'_handler') then mt.__index = c._handler end
264✔
148
    elseif base ~= nil then
480✔
UNCOV
149
        error("must derive from a table type",3)
×
150
    end
151

152
    c.__index = c
744✔
153
    setmetatable(c,mt)
744✔
154
    if not plain then
744✔
155
        if base and rawget(base,'_init') then c._parent_with_init = base end -- For super and inherited init
720✔
156
        c._init = nil
720✔
157
    end
158

159
    if base and rawget(base,'_class_init') then
744✔
160
        base._class_init(c,c_arg)
16✔
161
    end
162

163
    -- expose a ctor which can be called by <classname>(<args>)
164
    mt.__call = function(class_tbl,...)
165
        local obj
166
        if rawget(c,'_create') then obj = c._create(...) end
2,288✔
167
        if not obj then obj = {} end
1,904✔
168
        setmetatable(obj,c)
1,904✔
169

170
        if rawget(c,'_init') or rawget(c,'_parent_with_init') then -- constructor exists
1,904✔
171
            local res = call_ctor(c,obj,...)
1,896✔
172
            if res then -- _if_ a ctor returns a value, it becomes the object...
1,864✔
173
                obj = res
88✔
174
                setmetatable(obj,c)
88✔
175
            end
176
        end
177

178
        if base and rawget(base,'_post_init') then
1,872✔
UNCOV
179
            base._post_init(obj)
×
180
        end
181

182
        return obj
1,872✔
183
    end
184
    -- Call Class.catch to set a handler for methods/properties not found in the class!
185
    c.catch = function(self, handler)
186
        if type(self) == "function" then
24✔
187
            -- called using . instead of :
UNCOV
188
            handler = self
×
189
        end
190
        c._handler = handler
24✔
191
        mt.__index = handler
24✔
192
    end
193
    c.is_a = is_a
744✔
194
    c.class_of = class_of
744✔
195
    c.cast = cast
744✔
196
    c._class = c
744✔
197

198
    if not rawget(c,'__tostring') then
744✔
199
        c.__tostring = _class_tostring
472✔
200
    end
201

202
    return c
744✔
203
end
204

205
--- create a new class, derived from a given base class.
206
-- Supporting two class creation syntaxes:
207
-- either `Name = class(base)` or `class.Name(base)`.
208
-- The first form returns the class directly and does not set its `_name`.
209
-- The second form creates a variable `Name` in the current environment set
210
-- to the class, and also sets `_name`.
211
-- @function class
212
-- @param base optional base class
213
-- @param c_arg optional parameter to class constructor
214
-- @param c optional table to be used as class
215
local class
216
class = setmetatable({},{
368✔
217
    __call = function(fun,...)
218
        return _class(...)
688✔
219
    end,
220
    __index = function(tbl,key)
221
        if key == 'class' then
56✔
UNCOV
222
            io.stderr:write('require("pl.class").class is deprecated. Use require("pl.class")\n')
×
223
            return class
×
224
        end
225
        compat = compat or require 'pl.compat'
56✔
226
        local env = compat.getfenv(2)
56✔
227
        return function(...)
228
            local c = _class(...)
56✔
229
            c._name = key
56✔
230
            rawset(env,key,c)
56✔
231
            return c
56✔
232
        end
233
    end
234
})
184✔
235

236
class.properties = class()
276✔
237

238
function class.properties._class_init(klass)
368✔
239
    klass.__index = function(t,key)
240
        -- normal class lookup!
241
        local v = klass[key]
48✔
242
        if v then return v end
48✔
243
        -- is it a getter?
244
        v = rawget(klass,'get_'..key)
40✔
245
        if v then
40✔
246
            return v(t)
16✔
247
        end
248
        -- is it a field?
249
        return rawget(t,'_'..key)
24✔
250
    end
251
    klass.__newindex = function (t,key,value)
252
        -- if there's a setter, use that, otherwise directly set table
253
        local p = 'set_'..key
56✔
254
        local setter = klass[p]
56✔
255
        if setter then
56✔
256
            setter(t,value)
24✔
257
        else
258
            rawset(t,key,value)
40✔
259
        end
260
    end
261
end
262

263

264
return class
184✔
265

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc