vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 --[[
2 Name: AceLibrary
3 Revision: $Rev: 7540 $
4 Author(s): ckknight (ckknight@gmail.com)
5 cladhaire (cladhaire@gmail.com)
6 Inspired By: Iriel (iriel@vigilance-committee.org)
7 Tekkub (tekkub@gmail.com)
8 Website: http://www.wowace.com/
9 Documentation: http://wiki.wowace.com/index.php/AceLibrary
10 SVN: http://svn.wowace.com/root/trunk/Ace2/AceLibrary
11 Description: Versioning library to handle other library instances, upgrading,
12 and proper access.
13 It also provides a base for libraries to work off of, providing
14 proper error tools. It is handy because all the errors occur in the
15 file that called it, not in the library file itself.
16 Dependencies: None
17 ]]
18  
19 local ACELIBRARY_MAJOR = "AceLibrary"
20 local ACELIBRARY_MINOR = "$Revision: 7540 $"
21  
22 -- CHANGE DEBUG TO ``false`` ON RELEASE -------------------
23 local DEBUG = true
24 -- CHANGE DEBUG TO ``false`` ON RELEASE -------------------
25  
26 local _G = getfenv(0)
27 local previous = _G[ACELIBRARY_MAJOR]
28 if previous and not previous:IsNewVersion(ACELIBRARY_MAJOR, ACELIBRARY_MINOR) then return end
29  
30 -- @table AceLibrary
31 -- @brief System to handle all versioning of libraries.
32 local AceLibrary = {}
33 local AceLibrary_mt = {}
34 setmetatable(AceLibrary, AceLibrary_mt)
35  
36 local tmp
37 local function error(self, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
38 if not tmp then
39 tmp = {}
40 else
41 for k in pairs(tmp) do tmp[k] = nil end
42 table.setn(tmp, 0)
43 end
44  
45 tmp.n = 0
46 table.insert(tmp, a1)
47 table.insert(tmp, a2)
48 table.insert(tmp, a3)
49 table.insert(tmp, a4)
50 table.insert(tmp, a5)
51 table.insert(tmp, a6)
52 table.insert(tmp, a7)
53 table.insert(tmp, a8)
54 table.insert(tmp, a9)
55 table.insert(tmp, a10)
56 table.insert(tmp, a11)
57 table.insert(tmp, a12)
58 table.insert(tmp, a13)
59 table.insert(tmp, a14)
60 table.insert(tmp, a15)
61 table.insert(tmp, a16)
62 table.insert(tmp, a17)
63 table.insert(tmp, a18)
64 table.insert(tmp, a19)
65 table.insert(tmp, a20)
66  
67 local stack = debugstack()
68 if not message then
69 local _,_,second = string.find(stack, "\n(.-)\n")
70 message = "error raised! " .. second
71 else
72 for i = 1,table.getn(tmp) do
73 tmp[i] = tostring(tmp[i])
74 end
75 for i = 1,10 do
76 table.insert(tmp, "nil")
77 end
78 message = string.format(message, unpack(tmp))
79 end
80  
81 if getmetatable(self) and getmetatable(self).__tostring then
82 message = string.format("%s: %s", tostring(self), message)
83 elseif type(self.GetLibraryVersion) == "function" and AceLibrary:HasInstance(self:GetLibraryVersion()) then
84 message = string.format("%s: %s", self:GetLibraryVersion(), message)
85 elseif type(self.class) == "table" and type(self.class.GetLibraryVersion) == "function" and AceLibrary:HasInstance(self.class:GetLibraryVersion()) then
86 message = string.format("%s: %s", self.class:GetLibraryVersion(), message)
87 end
88  
89 local first = string.gsub(stack, "\n.*", "")
90 local file = string.gsub(first, ".*\\(.*).lua:%d+: .*", "%1")
91 file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
92  
93 local i = 0
94 for s in string.gfind(stack, "\n([^\n]*)") do
95 i = i + 1
96 if not string.find(s, file .. "%.lua:%d+:") then
97 file = string.gsub(s, "^.*\\(.*).lua:%d+: .*", "%1")
98 file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
99 break
100 end
101 end
102 local j = 0
103 for s in string.gfind(stack, "\n([^\n]*)") do
104 j = j + 1
105 if j > i and not string.find(s, file .. "%.lua:%d+:") then
106 _G.error(message, j + 1)
107 return
108 end
109 end
110 _G.error(message, 2)
111 return
112 end
113  
114 local function assert(self, condition, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
115 if not condition then
116 if not message then
117 local stack = debugstack()
118 local _,_,second = string.find(stack, "\n(.-)\n")
119 message = "assertion failed! " .. second
120 end
121 error(self, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
122 return
123 end
124 return condition
125 end
126  
127 local function argCheck(self, arg, num, kind, kind2, kind3, kind4, kind5)
128 if type(num) ~= "number" then
129 error(self, "Bad argument #3 to `argCheck' (number expected, got %s)", type(num))
130 elseif type(kind) ~= "string" then
131 error(self, "Bad argument #4 to `argCheck' (string expected, got %s)", type(kind))
132 end
133 local errored = false
134 arg = type(arg)
135 if arg ~= kind and arg ~= kind2 and arg ~= kind3 and arg ~= kind4 and arg ~= kind5 then
136 local _,_,func = string.find(debugstack(), "`argCheck'.-([`<].-['>])")
137 if not func then
138 _,_,func = string.find(debugstack(), "([`<].-['>])")
139 end
140 if kind5 then
141 error(self, "Bad argument #%s to %s (%s, %s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, kind5, arg)
142 elseif kind4 then
143 error(self, "Bad argument #%s to %s (%s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, arg)
144 elseif kind3 then
145 error(self, "Bad argument #%s to %s (%s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, arg)
146 elseif kind2 then
147 error(self, "Bad argument #%s to %s (%s or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, arg)
148 else
149 error(self, "Bad argument #%s to %s (%s expected, got %s)", tonumber(num) or 0/0, func, kind, arg)
150 end
151 end
152 end
153  
154 local function pcall(self, func, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
155 a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = _G.pcall(func, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
156 if not a1 then
157 error(self, string.gsub(a2, ".-%.lua:%d-: ", ""))
158 else
159 return a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20
160 end
161 end
162  
163 local recurse = {}
164 local function addToPositions(t, major)
165 if not AceLibrary.positions[t] or AceLibrary.positions[t] == major then
166 t[recurse] = true
167 AceLibrary.positions[t] = major
168 for k,v in pairs(t) do
169 if type(v) == "table" and not v[recurse] then
170 addToPositions(v, major)
171 end
172 if type(k) == "table" and not k[recurse] then
173 addToPositions(k, major)
174 end
175 end
176 local mt = getmetatable(t)
177 if mt and not mt[recurse] then
178 addToPositions(mt, major)
179 end
180 t[recurse] = nil
181 end
182 end
183  
184 local function svnRevisionToNumber(text)
185 if type(text) == "string" then
186 if string.find(text, "^%$Revision: (%d+) %$$") then
187 return tonumber((string.gsub(text, "^%$Revision: (%d+) %$$", "%1")))
188 elseif string.find(text, "^%$Rev: (%d+) %$$") then
189 return tonumber((string.gsub(text, "^%$Rev: (%d+) %$$", "%1")))
190 elseif string.find(text, "^%$LastChangedRevision: (%d+) %$$") then
191 return tonumber((string.gsub(text, "^%$LastChangedRevision: (%d+) %$$", "%1")))
192 end
193 elseif type(text) == "number" then
194 return text
195 end
196 return nil
197 end
198  
199 local crawlReplace
200 do
201 local recurse = {}
202 local function func(t, to, from)
203 if recurse[t] then
204 return
205 end
206 recurse[t] = true
207 local mt = getmetatable(t)
208 setmetatable(t, nil)
209 t[from], t[to] = nil, t[from]
210 for k,v in pairs(t) do
211 if v == from then
212 t[k] = to
213 elseif type(v) == "table" then
214 if not recurse[v] then
215 func(v, to, from)
216 end
217 end
218  
219 if type(k) == "table" then
220 if not recurse[k] then
221 func(k, to, from)
222 end
223 end
224 end
225 setmetatable(t, mt)
226 if mt then
227 if mt == from then
228 setmetatable(t, to)
229 elseif not recurse[mt] then
230 func(mt, to, from)
231 end
232 end
233 end
234 function crawlReplace(t, to, from)
235 func(t, to, from)
236 for k in pairs(recurse) do
237 recurse[k] = nil
238 end
239 end
240 end
241  
242 -- @function destroyTable
243 -- @brief remove all the contents of a table
244 -- @param t table to destroy
245 local function destroyTable(t)
246 setmetatable(t, nil)
247 for k,v in pairs(t) do t[k] = nil end
248 table.setn(t, 0)
249 end
250  
251 local function isFrame(frame)
252 return type(frame) == "table" and type(frame[0]) == "userdata" and type(frame.IsFrameType) == "function" and getmetatable(frame) and type(getmetatable(frame).__index) == "function"
253 end
254  
255 local new, del
256 do
257 local tables = setmetatable({}, {__mode = "k"})
258  
259 function new()
260 local t = next(tables)
261 if t then
262 tables[t] = nil
263 return t
264 else
265 return {}
266 end
267 end
268  
269 function del(t, depth)
270 if depth and depth > 0 then
271 for k,v in pairs(t) do
272 if type(v) == "table" and not isFrame(v) then
273 del(v, depth - 1)
274 end
275 end
276 end
277 destroyTable(t)
278 tables[t] = true
279 end
280 end
281  
282 -- @function copyTable
283 -- @brief Create a shallow copy of a table and return it.
284 -- @param from The table to copy from
285 -- @return A shallow copy of the table
286 local function copyTable(from)
287 local to = new()
288 for k,v in pairs(from) do to[k]=v end
289 table.setn(to, table.getn(from))
290 setmetatable(to, getmetatable(from))
291 return to
292 end
293  
294 -- @function deepTransfer
295 -- @brief Fully transfer all data, keeping proper previous table
296 -- backreferences stable.
297 -- @param to The table with which data is to be injected into
298 -- @param from The table whose data will be injected into the first
299 -- @param saveFields If available, a shallow copy of the basic data is saved
300 -- in here.
301 -- @param list The account of table references
302 -- @param list2 The current status on which tables have been traversed.
303 local deepTransfer
304 do
305 -- @function examine
306 -- @brief Take account of all the table references to be shared
307 -- between the to and from tables.
308 -- @param to The table with which data is to be injected into
309 -- @param from The table whose data will be injected into the first
310 -- @param list An account of the table references
311 local function examine(to, from, list, major)
312 list[from] = to
313 for k in pairs(from) do
314 if to[k] and type(from[k]) == "table" and type(to[k]) == "table" and not list[from[k]] then
315 if from[k] == to[k] then
316 list[from[k]] = to[k]
317 elseif AceLibrary.positions[from[v]] ~= major and AceLibrary.positions[from[v]] then
318 list[from[k]] = from[k]
319 elseif not list[from[k]] then
320 examine(to[k], from[k], list, major)
321 end
322 end
323 end
324 return list
325 end
326  
327 function deepTransfer(to, from, saveFields, major, list, list2)
328 setmetatable(to, nil)
329 local createdList
330 if not list then
331 createdList = true
332 list = new()
333 list2 = new()
334 examine(to, from, list, major)
335 end
336 list2[to] = to
337 for k,v in pairs(to) do
338 if type(from[k]) ~= "table" or type(v) ~= "table" or isFrame(v) then
339 if saveFields then
340 saveFields[k] = v
341 end
342 to[k] = nil
343 elseif v ~= _G then
344 if saveFields then
345 saveFields[k] = copyTable(v)
346 end
347 end
348 end
349 for k in pairs(from) do
350 if to[k] and to[k] ~= from[k] and AceLibrary.positions[to[k]] == major and from[k] ~= _G then
351 if not list2[to[k]] then
352 deepTransfer(to[k], from[k], nil, major, list, list2)
353 end
354 to[k] = list[to[k]] or list2[to[k]]
355 else
356 to[k] = from[k]
357 end
358 end
359 table.setn(to, table.getn(from))
360 setmetatable(to, getmetatable(from))
361 local mt = getmetatable(to)
362 if mt then
363 if list[mt] then
364 setmetatable(to, list[mt])
365 elseif mt.__index and list[mt.__index] then
366 mt.__index = list[mt.__index]
367 end
368 end
369 destroyTable(from)
370 if createdList then
371 del(list)
372 del(list2)
373 end
374 end
375 end
376  
377 -- @method IsNewVersion
378 -- @brief Obtain whether the supplied version would be an upgrade to the
379 -- current version. This allows for bypass code in library
380 -- declaration.
381 -- @param major A string representing the major version
382 -- @param minor An integer or an svn revision string representing the minor version
383 -- @return whether the supplied version would be newer than what is
384 -- currently available.
385 function AceLibrary:IsNewVersion(major, minor)
386 argCheck(self, major, 2, "string")
387 if type(minor) == "string" then
388 local m = svnRevisionToNumber(minor)
389 if m then
390 minor = m
391 else
392 _G.error(string.format("Bad argument #3 to `IsNewVersion'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
393 end
394 end
395 argCheck(self, minor, 3, "number")
396 local data = self.libs[major]
397 if not data then
398 return true
399 end
400 return data.minor < minor
401 end
402  
403 -- @method HasInstance
404 -- @brief Returns whether an instance exists. This allows for optional support of a library.
405 -- @param major A string representing the major version.
406 -- @param minor (optional) An integer or an svn revision string representing the minor version.
407 -- @return Whether an instance exists.
408 function AceLibrary:HasInstance(major, minor)
409 argCheck(self, major, 2, "string")
410 if minor then
411 if type(minor) == "string" then
412 local m = svnRevisionToNumber(minor)
413 if m then
414 minor = m
415 else
416 _G.error(string.format("Bad argument #3 to `HasInstance'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
417 end
418 end
419 argCheck(self, minor, 3, "number")
420 if not self.libs[major] then
421 return
422 end
423 return self.libs[major].minor == minor
424 end
425 return self.libs[major] and true
426 end
427  
428 -- @method GetInstance
429 -- @brief Returns the library with the given major/minor version.
430 -- @param major A string representing the major version.
431 -- @param minor (optional) An integer or an svn revision string representing the minor version.
432 -- @return The library with the given major/minor version.
433 function AceLibrary:GetInstance(major, minor)
434 argCheck(self, major, 2, "string")
435  
436 local data = self.libs[major]
437 if not data then
438 _G.error(string.format("Cannot find a library instance of %s.", major), 2)
439 return
440 end
441 if minor then
442 if type(minor) == "string" then
443 local m = svnRevisionToNumber(minor)
444 if m then
445 minor = m
446 else
447 _G.error(string.format("Bad argument #3 to `GetInstance'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
448 end
449 end
450 argCheck(self, minor, 2, "number")
451 if data.minor ~= minor then
452 _G.error(string.format("Cannot find a library instance of %s, minor version %d.", major, minor), 2)
453 return
454 end
455 end
456 return data.instance
457 end
458  
459 -- Syntax sugar. AceLibrary("FooBar-1.0")
460 AceLibrary_mt.__call = AceLibrary.GetInstance
461  
462 local donothing
463  
464 local AceEvent
465  
466 -- @method Register
467 -- @brief Registers a new version of a given library.
468 -- @param newInstance the library to register
469 -- @param major the major version of the library
470 -- @param minor the minor version of the library
471 -- @param activateFunc (optional) A function to be called when the library is
472 -- fully activated. Takes the arguments
473 -- (newInstance [, oldInstance, oldDeactivateFunc]). If
474 -- oldInstance is given, you should probably call
475 -- oldDeactivateFunc(oldInstance).
476 -- @param deactivateFunc (optional) A function to be called by a newer library's
477 -- activateFunc.
478 -- @param externalFunc (optional) A function to be called whenever a new
479 -- library is registered.
480 function AceLibrary:Register(newInstance, major, minor, activateFunc, deactivateFunc, externalFunc)
481 argCheck(self, newInstance, 2, "table")
482 argCheck(self, major, 3, "string")
483 if type(minor) == "string" then
484 local m = svnRevisionToNumber(minor)
485 if m then
486 minor = m
487 else
488 _G.error(string.format("Bad argument #4 to `Register'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
489 end
490 end
491 argCheck(self, minor, 4, "number")
492 if math.floor(minor) ~= minor or minor < 0 then
493 error(self, "Bad argument #4 to `Register' (integer >= 0 expected, got %s)", minor)
494 end
495 argCheck(self, activateFunc, 5, "function", "nil")
496 argCheck(self, deactivateFunc, 6, "function", "nil")
497 argCheck(self, externalFunc, 7, "function", "nil")
498 if not deactivateFunc then
499 if not donothing then
500 donothing = function() end
501 end
502 deactivateFunc = donothing
503 end
504 local data = self.libs[major]
505 if not data then
506 -- This is new
507 local instance = copyTable(newInstance)
508 crawlReplace(instance, instance, newInstance)
509 destroyTable(newInstance)
510 if AceLibrary == newInstance then
511 self = instance
512 AceLibrary = instance
513 end
514 self.libs[major] = {
515 instance = instance,
516 minor = minor,
517 deactivateFunc = deactivateFunc,
518 externalFunc = externalFunc,
519 }
520 function instance:GetLibraryVersion()
521 return major, minor
522 end
523 if not instance.error then
524 instance.error = error
525 end
526 if not instance.assert then
527 instance.assert = assert
528 end
529 if not instance.argCheck then
530 instance.argCheck = argCheck
531 end
532 if not instance.pcall then
533 instance.pcall = pcall
534 end
535 addToPositions(instance, major)
536 if activateFunc then
537 activateFunc(instance, nil, nil) -- no old version, so explicit nil
538 end
539  
540 if externalFunc then
541 for k,data in pairs(self.libs) do
542 if k ~= major then
543 externalFunc(instance, k, data.instance)
544 end
545 end
546 end
547  
548 for k,data in pairs(self.libs) do
549 if k ~= major and data.externalFunc then
550 data.externalFunc(data.instance, major, instance)
551 end
552 end
553 if major == "AceEvent-2.0" then
554 AceEvent = instance
555 end
556 if AceEvent then
557 AceEvent.TriggerEvent(self, "AceLibrary_Register", major, instance)
558 end
559  
560 return instance
561 end
562 local instance = data.instance
563 if minor <= data.minor then
564 if DEBUG then
565 -- This one is already obsolete, raise an error.
566 error(string.format("Obsolete library registered. %s is already registered at version %d. You are trying to register version %d. Hint: if not AceLibrary:IsNewVersion(%q, %d) then return end", major, data.minor, minor, major, minor), 2)
567 return
568 end
569 return instance
570 end
571 -- This is an update
572 local oldInstance = new()
573  
574 addToPositions(newInstance, major)
575 local isAceLibrary = (AceLibrary == newInstance)
576 local old_error, old_assert, old_argCheck, old_pcall
577 if isAceLibrary then
578 self = instance
579 AceLibrary = instance
580  
581 old_error = instance.error
582 old_assert = instance.assert
583 old_argCheck = instance.argCheck
584 old_pcall = instance.pcall
585  
586 self.error = error
587 self.assert = assert
588 self.argCheck = argCheck
589 self.pcall = pcall
590 end
591 deepTransfer(instance, newInstance, oldInstance, major)
592 crawlReplace(instance, instance, newInstance)
593 local oldDeactivateFunc = data.deactivateFunc
594 data.minor = minor
595 data.deactivateFunc = deactivateFunc
596 data.externalFunc = externalFunc
597 function instance:GetLibraryVersion()
598 return major, minor
599 end
600 if not instance.error then
601 instance.error = error
602 end
603 if not instance.assert then
604 instance.assert = assert
605 end
606 if not instance.argCheck then
607 instance.argCheck = argCheck
608 end
609 if not instance.pcall then
610 instance.pcall = pcall
611 end
612 if isAceLibrary then
613 for _,v in pairs(self.libs) do
614 local i = type(v) == "table" and v.instance
615 if type(i) == "table" then
616 if i.error == old_error or not i.error then
617 i.error = error
618 end
619 if i.assert == old_assert or not i.assert then
620 i.assert = assert
621 end
622 if i.argCheck == old_argCheck or not i.argCheck then
623 i.argCheck = argCheck
624 end
625 if i.pcall == old_pcall or not i.pcall then
626 i.pcall = pcall
627 end
628 end
629 end
630 end
631 if activateFunc then
632 activateFunc(instance, oldInstance, oldDeactivateFunc)
633 else
634 oldDeactivateFunc(oldInstance)
635 end
636 del(oldInstance)
637  
638 if externalFunc then
639 for k,data in pairs(self.libs) do
640 if k ~= major then
641 externalFunc(instance, k, data.instance)
642 end
643 end
644 end
645  
646 return instance
647 end
648  
649 local iter
650 function AceLibrary:IterateLibraries()
651 if not iter then
652 local function iter(t, k)
653 k = next(t, k)
654 if not k then
655 return nil
656 else
657 return k, t[k].instance
658 end
659 end
660 end
661 return iter, self.libs, nil
662 end
663  
664 -- @function Activate
665 -- @brief The activateFunc for AceLibrary itself. Called when
666 -- AceLibrary properly registers.
667 -- @param self Reference to AceLibrary
668 -- @param oldLib (optional) Reference to an old version of AceLibrary
669 -- @param oldDeactivate (optional) Function to deactivate the old lib
670 local function activate(self, oldLib, oldDeactivate)
671 if not self.libs then
672 if oldLib then
673 self.libs = oldLib.libs
674 end
675 if not self.libs then
676 self.libs = {}
677 end
678 end
679 if not self.positions then
680 if oldLib then
681 self.positions = oldLib.positions
682 end
683 if not self.positions then
684 self.positions = setmetatable({}, { __mode = "k" })
685 end
686 end
687  
688 -- Expose the library in the global environment
689 _G[ACELIBRARY_MAJOR] = self
690  
691 if oldDeactivate then
692 oldDeactivate(oldLib)
693 end
694 end
695  
696 if not previous then
697 previous = AceLibrary
698 end
699 if not previous.libs then
700 previous.libs = {}
701 end
702 AceLibrary.libs = previous.libs
703 if not previous.positions then
704 previous.positions = setmetatable({}, { __mode = "k" })
705 end
706 AceLibrary.positions = previous.positions
707 AceLibrary:Register(AceLibrary, ACELIBRARY_MAJOR, ACELIBRARY_MINOR, activate)