-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathLibDogTag-3.0.lua
623 lines (576 loc) · 19.4 KB
/
LibDogTag-3.0.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
--[[
Name: LibDogTag-3.0
Revision: @project-revision@
Website: https://www.wowace.com/projects/libdogtag-3-0
Description: A library to provide a markup syntax
]]
local MAJOR_VERSION = "LibDogTag-3.0"
local MINOR_VERSION = tonumber(("@project-date-integer@"):match("%d+")) or 33333333333333
if MINOR_VERSION > _G.DogTag_MINOR_VERSION then
_G.DogTag_MINOR_VERSION = MINOR_VERSION
end
local type, error, pairs, ipairs, next, pcall, _G = type, error, pairs, ipairs, next, pcall, _G
-- #AUTODOC_NAMESPACE DogTag
DogTag_funcs[#DogTag_funcs+1] = function(DogTag)
local L = DogTag.L
local newList, newSet, del, deepCopy = DogTag.newList, DogTag.newSet, DogTag.del, DogTag.deepCopy
local select2 = DogTag.select2
local fixNamespaceList = DogTag.fixNamespaceList
local memoizeTable = DogTag.memoizeTable
local deepCompare = DogTag.deepCompare
local kwargsToKwargTypes = DogTag.kwargsToKwargTypes
local kwargsToKwargTypesWithTableCache = DogTag.kwargsToKwargTypesWithTableCache
local fsNeedUpdate, fsNeedQuickUpdate, codeToFunction, codeToEventList, eventData, clearCodes
local clearCodes = DogTag.clearCodes
DogTag_funcs[#DogTag_funcs+1] = function()
fsNeedUpdate = DogTag.fsNeedUpdate
fsNeedQuickUpdate = DogTag.fsNeedQuickUpdate
codeToFunction = DogTag.codeToFunction
codeToEventList = DogTag.codeToEventList
eventData = DogTag.eventData
end
local fsToFrame, fsToCode, fsToNSList, fsToKwargs, Tags, AddonFinders
if DogTag.oldLib then
local oldLib = DogTag.oldLib
fsToFrame = oldLib.fsToFrame
fsToCode = oldLib.fsToCode
fsToNSList = oldLib.fsToNSList
fsToKwargs = oldLib.fsToKwargs
local tmp = {}
for fs, kwargs in pairs(fsToKwargs) do
tmp[fs] = memoizeTable(deepCopy(kwargs))
fsToKwargs[fs] = nil
end
for fs, kwargs in pairs(tmp) do
fsToKwargs[fs] = kwargs
end
tmp = nil
Tags = oldLib.Tags
Tags["Base"] = {}
AddonFinders = oldLib.AddonFinders
AddonFinders["Base"] = {}
else
fsToFrame = {}
fsToCode = {}
fsToNSList = {}
fsToKwargs = {}
Tags = { ["Base"] = {} }
AddonFinders = { ["Base"] = {} }
end
DogTag.fsToFrame = fsToFrame
DogTag.fsToCode = fsToCode
DogTag.fsToNSList = fsToNSList
DogTag.fsToKwargs = fsToKwargs
DogTag.Tags = Tags
DogTag.AddonFinders = AddonFinders
local sortStringList = DogTag.sortStringList
DogTag.__colors = {
minHP = { 1, 0, 0 },
midHP = { 1, 1, 0 },
maxHP = { 0, 1, 0 },
unknown = { 0.8, 0.8, 0.8 },
hostile = { 226/255, 45/255, 75/255 },
neutral = { 1, 1, 34/255 },
friendly = { 0.2, 0.8, 0.15 },
civilian = { 48/255, 113/255, 191/255 },
dead = { 0.6, 0.6, 0.6 },
tapped = { 0.5, 0.5, 0.5 },
disconnected = { 0.7, 0.7, 0.7 },
petHappy = { 0, 1, 0 },
petNeutral = { 1, 1, 0 },
petAngry = { 1, 0, 0 },
rage = { 226/255, 45/255, 75/255 },
energy = { 1, 220/255, 25/255 },
focus = { 1, 210/255, 0 },
mana = { 48/255, 113/255, 191/255 },
runicPower = { 0, 209/255, 1 },
}
local function updateClassColors()
local classColors = _G.CUSTOM_CLASS_COLORS or _G.RAID_CLASS_COLORS
for class, data in pairs(classColors) do
DogTag.__colors[class] = { data.r, data.g, data.b, }
end
end
updateClassColors()
--[[
Notes:
Adds a tag to the specified namespace
Arguments:
string - namespace to add to
string - name of the tag
table - data of the tag
Example:
LibStub("LibDogTag-3.0"):AddTag("MyNamespace", "Square", {
code = function(number) -- actual function that will be called
return number * number
end,
arg = {
'number', 'number', '@req', -- name, types, default
},
ret = 'number', -- return value
events = "SOME_EVENT#$number", -- will update when SOME_EVENT with the argument `number` is dispatched
doc = "Return the square of number", -- the description
example = '[4:Square] => "16"; [5:Square] => "25"', -- show one or more examples in this format
category = "Category name",
})
]]
function DogTag:AddTag(namespace, tag, data)
if type(namespace) ~= "string" then
error(("Bad argument #2 to `AddTag'. Expected %q, got %q"):format("string", type(namespace)), 2)
end
if type(tag) ~= "string" then
error(("Bad argument #3 to `AddTag'. Expected %q, got %q"):format("string", type(tag)), 2)
end
if type(data) ~= "table" then
error(("Bad argument #4 to `AddTag'. Expected %q, got %q"):format("table", type(data)), 2)
end
if not Tags[namespace] then
Tags[namespace] = {}
end
if Tags["Base"][tag] or Tags[namespace][tag] then
error(("Bad argument #3 to `AddTag'. %q already registered"):format(tag), 2)
end
local tagData = newList()
Tags[namespace][tag] = tagData
local arg = data.arg
if arg then
if type(arg) ~= "table" then
error("arg must be a table", 2)
end
if #arg % 3 ~= 0 then
error("arg must be a table with a length a multiple of 3", 2)
end
for i = 1, #arg, 3 do
local key, types, default = arg[i], arg[i+1], arg[i+2]
if type(key) ~= "string" then
error("arg must have its keys as strings", 2)
end
if type(types) ~= "string" then
error("arg must have its types as strings", 2)
end
if types:match("^tuple%-") then
if key ~= "..." then
error("arg must have its key be ... if it is a tuple.", 2)
end
local tupleTypes = types:sub(7)
local t = newSet((';'):split(tupleTypes))
for k in pairs(t) do
if k ~= "nil" and k ~= "number" and k ~= "string" and k ~= "boolean" then
error("arg can only have tuples of nil, number, string, or boolean", 2)
end
end
if t["boolean"] and (next(t, "boolean") or next(t) ~= "boolean") then
error("arg cannot specify both boolean and something else", 2)
end
t = del(t)
arg[i+1] = "tuple-" .. sortStringList(tupleTypes)
else
local t = newSet((';'):split(types))
for k in pairs(t) do
if k ~= "nil" and k ~= "number" and k ~= "string" and k ~= "undef" and k ~= "boolean" then
error("arg must have nil, number, string, undef, boolean, or tuple", 2)
end
end
if not key:match("^[a-z]+$") then
error("arg must have its key be a string of lowercase letters.", 2)
end
if t["nil"] and t["undef"] then
error("arg cannot specify both nil and undef", 2)
end
if t["boolean"] and (next(t, "boolean") or next(t) ~= "boolean") then
error("arg cannot specify both boolean and something else", 2)
end
t = del(t)
arg[i+1] = sortStringList(types)
end
end
tagData.arg = arg
end
if data.alias then
if type(data.alias) == "string" then
tagData.alias = data.alias
else -- function
tagData.alias = data.alias()
tagData.aliasFunc = data.alias
end
else
local ret = data.ret
if type(ret) == "string" then
tagData.ret = sortStringList(ret)
if ret then
local rets = newSet((";"):split(ret))
for k in pairs(rets) do
if k ~= "nil" and k ~= "number" and k ~= "string" and k ~= "boolean" then
error("ret must have nil, number, string, or boolean", 2)
end
end
rets = del(rets)
end
elseif type(ret) == "function" then
tagData.ret = ret
else
error(("ret must be a string or a function which returns a string, got %s"):format(type(ret)), 2)
end
if data.events then
if type(data.events) == "string" then
tagData.events = sortStringList(data.events)
elseif type(data.events) == "function" then
tagData.events = data.events
else
error(("events must be a string, function, or nil, got %s"):format(type(data.events)), 2)
end
end
tagData.alias = data.fakeAlias
if type(data) == "function" then
tagData.static = data.static
else
tagData.static = data.static and true or nil
end
if tagData.static and tagData.events then
error("Cannot specify both static and events", 2)
end
if type(data.code) ~= "function" then
error(("code must be a function, got %s"):format(type(data.code)), 2)
end
tagData.code = data.code
tagData.dynamicCode = data.dynamicCode and true or nil
end
tagData.doc = data.doc
if data.doc and type(data.doc) ~= "string" then
error(("doc must be nil or a string, got %s"):format(type(data.doc)), 2)
end
tagData.example = data.example
if data.doc then
if type(data.example) ~= "string" then
error(("if doc is supplied, example must be a string, got %s"):format(type(data.example)), 2)
end
local examples = newList((";"):split(data.example))
for i, v in ipairs(examples) do
if not v:trim():match("^%[.*%] => \".*\"$") then
error(("example must be in the form of [Tag sequence] => \"Result\", %s is not in said form."):format(v:trim()))
end
end
else
if data.example then
error("if doc is not supplied, example must be nil", 2)
end
end
tagData.category = data.category
if data.doc then
if type(data.category) ~= "string" then
error(("if doc is supplied, category must be a string, got %s"):format(type(data.category)), 2)
end
else
if data.category then
error("if doc is not supplied, category must be nil", 2)
end
end
if data.noDoc and type(data.doc) ~= "nil" then
error(("doc must be nil if noDoc is true, got %s"):format(type(data.doc)), 2)
end
tagData.noDoc = data.noDoc
del(data)
clearCodes(namespace)
end
local function updateFontString(fs)
fsNeedUpdate[fs] = nil
fsNeedQuickUpdate[fs] = nil
local code = fsToCode[fs]
if code then
local nsList = fsToNSList[fs]
local kwargs = fsToKwargs[fs]
local kwargTypes = kwargsToKwargTypesWithTableCache[kwargs]
local func = codeToFunction[nsList][kwargTypes][code]
DogTag.__isMouseOver = DogTag.__lastMouseover == fsToFrame[fs]
local success, text, opacity, outline = pcall(func, kwargs)
if not success then
DogTag.tagError(code, nsList, text)
return
end
if success then
fs:SetText(text)
if opacity then
fs:SetAlpha(opacity)
end
local a, b, c = fs:GetFont()
if c ~= (outline or '') then
fs:SetFont(a, b, outline or '')
end
end
end
end
DogTag.updateFontString = updateFontString
--[[
Notes:
Manually updates a FontString previously registered.
Arguments:
frame - the FontString previously registered
Example:
LibStub("LibDogTag-3.0"):UpdateFontString(fs)
]]
function DogTag:UpdateFontString(fs)
local code = fsToCode[fs]
if code then
updateFontString(fs)
end
end
--[[
Notes:
Manually updates all FontStrings on a specified frame.
Arguments:
frame - the frame which to update all FontStrings on
Example:
LibStub("LibDogTag-3.0"):UpdateAllForFrame(frame)
]]
function DogTag:UpdateAllForFrame(frame)
for fs, f in pairs(fsToFrame) do
if frame == f then
updateFontString(fs)
end
end
end
--[[
Notes:
Adds a FontString to LibDogTag-3.0's registry, which will be updated automatically.
You can add twice without removing. It will just overwrite the previous registration.
You can specify any number of namespaces. "Base" is always included as a namespace.
The kwargs table is optional and always goes on the end after the namespaces. You can recycle the table after registering.
Arguments:
frame - the FontString to register
frame - the Frame which holds the FontString
string - the tag sequence
[optional] string - a semicolon-separated list of namespaces. Base is implied
[optional] table - a dictionary of default kwargs for all tags in the code to receive
Example:
LibStub("LibDogTag-3.0"):AddFontString(fs, fs:GetParent(), "[Name]", "Unit", { unit = 'mouseover' })
LibStub("LibDogTag-3.0"):AddFontString(fs, fs:GetParent(), "[Tag]", "MyNamespace")
LibStub("LibDogTag-3.0"):AddFontString(fs, fs:GetParent(), "[Tag] [Name]", "MyNamespace;Unit", { value = 5, unit = 'player', }) -- two namespaces at once
]]
function DogTag:AddFontString(fs, frame, code, nsList, kwargs)
if type(fs) ~= "table" then
error(("Bad argument #2 to `AddFontString'. Expected %q, got %q."):format("table", type(fs)), 2)
elseif type(frame) ~= "table" then
error(("Bad argument #3 to `AddFontString'. Expected %q, got %q."):format("table", type(frame)), 2)
elseif type(code) ~= "string" then
error(("Bad argument #4 to `AddFontString'. Expected %q, got %q."):format("string", type(code)), 2)
elseif nsList and type(nsList) ~= "string" then
error(("Bad argument #5 to `AddFontString'. Expected %q, got %q."):format("string", type(nsList)), 2)
elseif kwargs and type(kwargs) ~= "table" then
error(("Bad argument #6 to `AddFontString'. Expected %q, got %q."):format("table", type(kwargs)), 2)
end
nsList = fixNamespaceList[nsList]
--[[ Cybeloras 7-4-2012:
Noticed a massive performance bottleneck in this function, and this is what I discovered:
Using a kwargs table of {color=true,group=3,icons=108} (a pretty normal table, just 3 values),
the following were the results of two different methods to test if the tables are the same:
Over 500,000 iterations:
memoizeTable(deepCopy(kwargs)) averaged an execution time of 0.0270833 seconds
deepCompare(fsToKwargs[fs], kwargs) averaged an execution time of 0.0038213 seconds
Giving a 608.74% speed advantage to deepCompare
If we deepCompare first to see if we can return early (which should happen almost all the time,
because kwargs change very infrequently), the performance boost is massive. If deepCompare reveals that the kwargs have changed,
then we can go on to memoizeTable for the rest of the function.
Code changes:
Moved ( kwargs = memoizeTable(deepCopy(kwargs)) ) down below the if block
Changed ( fsToKwargs[fs] == kwargs ) to ( deepCompare(fsToKwargs[fs], kwargs) )
Added function DogTag.deepCompare to Helpers.lua
Added deepCompare = DogTag.deepCompare upvalue to this file's header
]]
if fsToCode[fs] then
if fsToFrame[fs] == frame and fsToCode[fs] == code and fsToNSList[fs] == nsList and deepCompare(fsToKwargs[fs], kwargs) then
fsNeedUpdate[fs] = true
return
end
self:RemoveFontString(fs)
end
kwargs = memoizeTable(deepCopy(kwargs))
fsToFrame[fs] = frame
fsToCode[fs] = code
fsToNSList[fs] = nsList
fsToKwargs[fs] = kwargs
local kwargTypes = kwargsToKwargTypesWithTableCache[kwargs]
local codeToEventList_nsList_kwargTypes_code = codeToEventList[nsList][kwargTypes][code]
if codeToEventList_nsList_kwargTypes_code == nil then
local _ = codeToFunction[nsList][kwargTypes][code] -- i guess this is just to invoke a metamethod. everybody loves commented code!
codeToEventList_nsList_kwargTypes_code = codeToEventList[nsList][kwargTypes][code]
if codeToEventList_nsList_kwargTypes_code == nil then
local _, minor = LibStub(MAJOR_VERSION)
error(("%s.%d: Error with code %q (%s). Event list not created. Please inform ckknight."):format(MAJOR_VERSION, minor, code, nsList))
end
end
if codeToEventList_nsList_kwargTypes_code then
for event, arg in pairs(codeToEventList_nsList_kwargTypes_code) do
eventData[event][fs] = arg
DogTag.eventUsed(event)
end
end
updateFontString(fs)
end
--[[
Notes:
Removes a registered FontString from LibDogTag-3.0's registry.
You can remove twice without issues.
Arguments:
frame - the FontString previously registered
Example:
LibStub("LibDogTag-3.0"):RemoveFontString(fs)
]]
function DogTag:RemoveFontString(fs)
if type(fs) ~= "table" then
error(("Bad argument #2 to `RemoveFontString'. Expected %q, got %q"):format("table", type(fs)), 2)
end
local code = fsToCode[fs]
if not code then
return
end
local frame = fsToFrame[fs]
local nsList = fsToNSList[fs]
local kwargs = fsToKwargs[fs]
fsToCode[fs], fsToFrame[fs], fsToNSList[fs], fsToKwargs[fs] = nil, nil, nil, nil
fsNeedUpdate[fs], fsNeedQuickUpdate[fs] = nil, nil
local kwargTypes = kwargsToKwargTypesWithTableCache[kwargs]
local codeToEventList_nsList_kwargTypes_code = codeToEventList[nsList][kwargTypes][code]
if codeToEventList_nsList_kwargTypes_code then
for event in pairs(codeToEventList_nsList_kwargTypes_code) do
eventData[event][fs] = nil
end
end
fs:SetText(nil)
local a, b = fs:GetFont()
fs:SetFont(a, b, "")
end
--[[
Notes:
Adds a handler to be called when an addon or library comes into being
This should only really be called by sublibraries or addons that register tags.
Arguments:
string - namespace the addon finder is associated with
string - "_G" for a value on the global table or "LibStub", "Rock", "AceLibrary" for a library of the specified type
string - name of the addon or library
function - function to be called when addon or library is found
Example:
LibStub("DogTag-3.0"):AddAddonFinder("MyNamespace", "LibStub", "LibMonkey-1.0", function(LibMonkey)
-- do something with LibMonkey now
end)
]]
function DogTag:AddAddonFinder(namespace, kind, name, func)
if type(namespace) ~= "string" then
error(("Bad argument #2 to `AddAddonFinder'. Expected %q, got %q"):format("string", type(namespace)), 2)
end
if type(kind) ~= "string" then
error(("Bad argument #3 to `AddAddonFinder'. Expected %q, got %q"):format("string", type(kind)), 2)
end
if kind ~= "_G" and kind ~= "LibStub" and kind ~= "Rock" and kind ~= "AceLibrary" then
error(("Bad argument #3 to `AddAddonFinder'. Expected %q, %q, %q or %q, got %q"):format("_G", "LibStub", "Rock", "AceLibrary", kind), 2)
end
if type(name) ~= "string" then
error(("Bad argument #4 to `AddAddonFinder'. Expected %q, got %q"):format("string", type(name)), 2)
end
if type(func) ~= "function" then
error(("Bad argument #5 to `AddAddonFinder'. Expected %q, got %q"):format("function", type(func)), 2)
end
if not AddonFinders[namespace] then
AddonFinders[namespace] = {}
end
AddonFinders[namespace][newList(kind, name, func)] = true
end
local inADDON_LOADED = false
local accessed_ADDON_LOADED = false
function DogTag:ADDON_LOADED()
if inADDON_LOADED then
accessed_ADDON_LOADED = true
return
end
inADDON_LOADED = true
accessed_ADDON_LOADED = false
for namespace, data in pairs(AddonFinders) do
local refresh = false
for k in pairs(data) do
local kind, name, func = k[1], k[2], k[3]
if kind == "_G" then
if _G[name] then
data[k] = nil
del(k)
func(_G[name])
refresh = true
end
elseif kind == "AceLibrary" then
if AceLibrary and AceLibrary:HasInstance(name) then
data[k] = nil
del(k)
func(AceLibrary(name))
refresh = true
end
elseif kind == "Rock" then
if Rock and Rock:HasLibrary(name) then
data[k] = nil
del(k)
func(Rock:GetLibrary(name))
refresh = true
end
elseif kind == "LibStub" then
if Rock then
Rock:HasLibrary(name) -- try to load
end
if AceLibrary then
AceLibrary:HasInstance(name) -- try to load
end
(C_AddOns and C_AddOns.LoadAddOn or _G.LoadAddOn)(name)
if LibStub:GetLibrary(name, true) then
data[k] = nil
del(k)
func(LibStub:GetLibrary(name))
refresh = true
end
end
end
if refresh then
clearCodes(namespace)
end
end
inADDON_LOADED = false
if accessed_ADDON_LOADED then
self:ADDON_LOADED()
end
end
--[[
Notes:
Clears a namespace's tags and any other relevant data.
This should be called when a sublibrary is upgrading.
Arguments:
string - namespace that is to be cleared
Example:
LibStub("LibDogTag-3.0"):ClearNamespace("MyNamespace")
]]
function DogTag:ClearNamespace(namespace)
if type(namespace) ~= "string" then
error(("Bad argument #2 to `ClearNamespace'. Expected %q, got %q"):format("string", type(namespace)), 2)
end
Tags[namespace] = nil
AddonFinders[namespace] = nil
self.EventHandlers[namespace] = nil
self.TimerHandlers[namespace] = nil
clearCodes(namespace)
collectgarbage('collect')
end
local function updateAllFontStrings()
for fs in pairs(fsToFrame) do
updateFontString(fs)
end
end
local function updateAllClassColors()
updateClassColors()
updateAllFontStrings()
end
function DogTag:PLAYER_LOGIN()
updateClassColors()
if _G.CUSTOM_CLASS_COLORS then
_G.CUSTOM_CLASS_COLORS:RegisterCallback(updateAllClassColors)
end
end
function DogTag:UnregisterCustomClassColors()
if _G.CUSTOM_CLASS_COLORS then
_G.CUSTOM_CLASS_COLORS:UnregisterCallback(updateAllClassColors)
end
end
end