Files
libdogtag-3-0/LibDogTag-3.0.lua
2008-03-30 00:13:47 +00:00

572 lines
17 KiB
Lua

--[[
Name: LibDogTag-3.0
Revision: $Rev$
Author: Cameron Kenneth Knight (ckknight@gmail.com)
Website: http://www.wowace.com/
Description: A library to provide a markup syntax
]]
local MAJOR_VERSION = "LibDogTag-3.0"
local MINOR_VERSION = tonumber(("$Revision$"):match("%d+")) or 0
if MINOR_VERSION > _G.DogTag_MINOR_VERSION then
_G.DogTag_MINOR_VERSION = MINOR_VERSION
end
-- #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 getNamespaceList = DogTag.getNamespaceList
local memoizeTable = DogTag.memoizeTable
local kwargsToKwargTypes = DogTag.kwargsToKwargTypes
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 },
}
for class, data in pairs(_G.RAID_CLASS_COLORS) do
DogTag.__colors[class] = { data.r, data.g, data.b, }
end
--[[
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
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
del(data)
clearCodes(namespace)
end
local call__func, call__kwargs, call__code, call__nsList
local function call()
return call__func(call__kwargs)
end
local function errorhandler(err)
local _, minor = LibStub(MAJOR_VERSION)
return geterrorhandler()(("%s.%d: Error with code %q%s. %s"):format(MAJOR_VERSION, minor, call__code, call__nsList == "Base" and "" or " (" .. call__nsList .. ")", err))
end
local function updateFontString(fs)
fsNeedUpdate[fs] = nil
fsNeedQuickUpdate[fs] = nil
local code = fsToCode[fs]
assert(code)
local nsList = fsToNSList[fs]
local kwargs = fsToKwargs[fs]
local kwargTypes = kwargsToKwargTypes[kwargs]
local func = codeToFunction[nsList][kwargTypes][code]
DogTag.__isMouseOver = DogTag.__lastMouseover == fsToFrame[fs]
call__func, call__kwargs, call__code, call__nsList = func, kwargs, code, nsList
local success, ret, alpha, outline = xpcall(call, errorhandler)
call__func, call__kwargs, call__code, call__nsList = nil, nil, nil, nil
if success then
fs:SetText(ret)
if alpha then
fs:SetAlpha(alpha)
end
local a, b = fs:GetFont()
fs:SetFont(a, b, outline or '')
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
tuple - extra namespaces to register with, can be in any order
[optional] kwargs - 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, ...)
if type(fs) ~= "table" then
error(("Bad argument #2 to `AddFontString'. Expected %q, got %q."):format("table", type(fs)), 2)
end
if type(frame) ~= "table" then
error(("Bad argument #3 to `AddFontString'. Expected %q, got %q."):format("table", type(frame)), 2)
end
if type(code) ~= "string" then
error(("Bad argument #4 to `AddFontString'. Expected %q, got %q."):format("string", type(code)), 2)
end
local n = select('#', ...)
local kwargs
if n > 0 then
kwargs = select(n, ...)
if type(kwargs) == "table" then
n = n - 1
else
kwargs = nil
end
end
for i = 1, n do
if type(select(i, ...)) ~= "string" then
error(("Bad argument #%d to `AddFontString'. Expected %q, got %q"):format(i+4, "string", type(select(i, ...))), 2)
end
end
local nsList = getNamespaceList(select2(1, n, ...))
kwargs = memoizeTable(deepCopy(kwargs))
if fsToCode[fs] then
if fsToFrame[fs] == frame and fsToCode[fs] == code and fsToNSList[fs] == nsList and fsToKwargs[fs] == kwargs then
fsNeedUpdate[fs] = true
return
end
self:RemoveFontString(fs)
end
fsToFrame[fs] = frame
fsToCode[fs] = code
fsToNSList[fs] = nsList
fsToKwargs[fs] = kwargs
local kwargTypes = kwargsToKwargTypes[kwargs]
local codeToEventList_nsList_kwargTypes_code = codeToEventList[nsList][kwargTypes][code]
if codeToEventList_nsList_kwargTypes_code == nil then
local _ = codeToFunction[nsList][kwargTypes][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 == "Base" and "" or " (" .. nsList .. ")"))
end
end
if codeToEventList_nsList_kwargTypes_code then
for event, arg in pairs(codeToEventList_nsList_kwargTypes_code) do
eventData[event][fs] = arg
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 = kwargsToKwargTypes[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
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
end