/trunk/FuBar_RecapFu/libs/AceLibrary
--[[ |
Name: AceLibrary |
Revision: $Rev: 29796 $ |
Revision: $Rev: 1091 $ |
Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) |
Inspired By: Iriel (iriel@vigilance-committee.org) |
Tekkub (tekkub@gmail.com) |
Revision: $Rev: 29796 $ |
Revision: $Rev: 1091 $ |
Website: http://www.wowace.com/ |
Documentation: http://www.wowace.com/index.php/AceLibrary |
SVN: http://svn.wowace.com/root/trunk/Ace2/AceLibrary |
SVN: http://svn.wowace.com/wowace/trunk/Ace2/AceLibrary |
Description: Versioning library to handle other library instances, upgrading, |
and proper access. |
It also provides a base for libraries to work off of, providing |
]] |
local ACELIBRARY_MAJOR = "AceLibrary" |
local ACELIBRARY_MINOR = "$Revision: 29796 $" |
local ACELIBRARY_MINOR = 90000 + tonumber(("$Revision: 1091 $"):match("(%d+)")) |
local _G = getfenv(0) |
local previous = _G[ACELIBRARY_MAJOR] |
if previous and not previous:IsNewVersion(ACELIBRARY_MAJOR, ACELIBRARY_MINOR) then return end |
do |
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info |
-- LibStub is hereby placed in the Public Domain -- Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke |
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS! |
local LibStub = _G[LIBSTUB_MAJOR] |
if not LibStub or LibStub.minor < LIBSTUB_MINOR then |
LibStub = LibStub or {libs = {}, minors = {} } |
_G[LIBSTUB_MAJOR] = LibStub |
LibStub.minor = LIBSTUB_MINOR |
function LibStub:NewLibrary(major, minor) |
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") |
minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") |
local oldminor = self.minors[major] |
if oldminor and oldminor >= minor then return nil end |
self.minors[major], self.libs[major] = minor, self.libs[major] or {} |
return self.libs[major], oldminor |
end |
function LibStub:GetLibrary(major, silent) |
if not self.libs[major] and not silent then |
error(("Cannot find a library instance of %q."):format(tostring(major)), 2) |
end |
return self.libs[major], self.minors[major] |
end |
function LibStub:IterateLibraries() return pairs(self.libs) end |
setmetatable(LibStub, { __call = LibStub.GetLibrary }) |
end |
end |
local LibStub = _G.LibStub |
-- If you don't want AceLibrary to enable libraries that are LoadOnDemand but |
-- disabled in the addon screen, set this to true. |
local DONT_ENABLE_LIBRARIES = nil |
local function safecall(func,...) |
local success, err = pcall(func,...) |
if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("\n(.-: )in.-\n") or "") .. err) end |
end |
local WoW22 = false |
if type(GetBuildInfo) == "function" then |
local success, buildinfo = pcall(GetBuildInfo) |
if success and type(buildinfo) == "string" then |
local num = tonumber(buildinfo:match("^(%d+%.%d+)")) |
if num and num >= 2.2 then |
WoW22 = true |
end |
end |
end |
-- @table AceLibrary |
-- @brief System to handle all versioning of libraries. |
local AceLibrary = {} |
local stack = debugstack() |
if not message then |
local _,_,second = stack:find("\n(.-)\n") |
local second = stack:match("\n(.-)\n") |
message = "error raised! " .. second |
else |
local arg = { ... } -- not worried about table creation, as errors don't happen often |
return _G.error(message, 2) |
end |
local assert |
if not WoW22 then |
function assert(self, condition, message, ...) |
if not condition then |
if not message then |
local stack = debugstack() |
local _,_,second = stack:find("\n(.-)\n") |
message = "assertion failed! " .. second |
end |
return error(self, message, ...) |
end |
return condition |
end |
end |
local type = type |
local function argCheck(self, arg, num, kind, kind2, kind3, kind4, kind5) |
if type(num) ~= "number" then |
return error(self, "Bad argument #3 to `argCheck' (number expected, got %s)", type(num)) |
elseif type(kind) ~= "string" then |
return error(self, "Bad argument #4 to `argCheck' (string expected, got %s)", type(kind)) |
end |
local errored = false |
arg = type(arg) |
if arg ~= kind and arg ~= kind2 and arg ~= kind3 and arg ~= kind4 and arg ~= kind5 then |
local stack = debugstack() |
local _,_,func = stack:find("`argCheck'.-([`<].-['>])") |
local func = stack:match("`argCheck'.-([`<].-['>])") |
if not func then |
_,_,func = stack:find("([`<].-['>])") |
func = stack:match("([`<].-['>])") |
end |
if kind5 then |
return 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) |
end |
local function svnRevisionToNumber(text) |
if type(text) == "string" then |
local kind = type(text) |
if kind == "number" or tonumber(text) then |
return tonumber(text) |
elseif kind == "string" then |
if text:find("^%$Revision: (%d+) %$$") then |
return tonumber((text:gsub("^%$Revision: (%d+) %$$", "%1"))) |
return tonumber((text:match("^%$Revision: (%d+) %$$"))) |
elseif text:find("^%$Rev: (%d+) %$$") then |
return tonumber((text:gsub("^%$Rev: (%d+) %$$", "%1"))) |
return tonumber((text:match("^%$Rev: (%d+) %$$"))) |
elseif text:find("^%$LastChangedRevision: (%d+) %$$") then |
return tonumber((text:gsub("^%$LastChangedRevision: (%d+) %$$", "%1"))) |
return tonumber((text:match("^%$LastChangedRevision: (%d+) %$$"))) |
end |
elseif type(text) == "number" then |
return text |
end |
return nil |
end |
-- @brief Create a shallow copy of a table and return it. |
-- @param from The table to copy from |
-- @return A shallow copy of the table |
local function copyTable(from) |
local to = {} |
local function copyTable(from, to) |
if not to then |
to = {} |
end |
for k,v in pairs(from) do |
to[k] = v |
end |
end |
end |
local function TryToEnable(addon) |
if DONT_ENABLE_LIBRARIES then return end |
local isondemand = IsAddOnLoadOnDemand(addon) |
if isondemand then |
local _, _, _, enabled = GetAddOnInfo(addon) |
EnableAddOn(addon) |
local _, _, _, _, loadable = GetAddOnInfo(addon) |
if not loadable and not enabled then |
DisableAddOn(addon) |
end |
return loadable |
end |
end |
-- @method TryToLoadStandalone |
-- @brief Attempt to find and load a standalone version of the requested library |
-- @param major A string representing the major version |
-- @return If library is found, return values from the call to LoadAddOn are returned |
-- @return If library is found and loaded, true is return. If not loadable, false is returned. |
-- If the library has been requested previously, nil is returned. |
local function TryToLoadStandalone(major) |
if not AceLibrary.scannedlibs then AceLibrary.scannedlibs = {} end |
AceLibrary.scannedlibs[major] = true |
local name, _, _, enabled, loadable = GetAddOnInfo(major) |
loadable = (enabled and loadable) or TryToEnable(name) |
local loaded = false |
if loadable then |
return LoadAddOn(name) |
loaded = true |
LoadAddOn(name) |
end |
for i=1,GetNumAddOns() do |
if GetAddOnMetadata(i, "X-AceLibrary-"..major) then |
local name, _, _, enabled, loadable = GetAddOnInfo(i) |
local field = "X-AceLibrary-" .. major |
for i = 1, GetNumAddOns() do |
if GetAddOnMetadata(i, field) then |
name, _, _, enabled, loadable = GetAddOnInfo(i) |
loadable = (enabled and loadable) or TryToEnable(name) |
if loadable then |
return LoadAddOn(name) |
loaded = true |
LoadAddOn(name) |
end |
end |
end |
return loaded |
end |
-- @method IsNewVersion |
end |
end |
argCheck(self, minor, 3, "number") |
local lib, oldMinor = LibStub:GetLibrary(major, true) |
if lib then |
return oldMinor < minor |
end |
local data = self.libs[major] |
if not data then |
return true |
if minor ~= false then |
TryToLoadStandalone(major) |
end |
local lib, ver = LibStub:GetLibrary(major, true) |
if not lib and self.libs[major] then |
lib, ver = self.libs[major].instance, self.libs[major].minor |
end |
if minor then |
if type(minor) == "string" then |
local m = svnRevisionToNumber(minor) |
end |
end |
argCheck(self, minor, 3, "number") |
if not self.libs[major] then |
return |
if not lib then |
return false |
end |
return self.libs[major].minor == minor |
return ver == minor |
end |
return self.libs[major] and true |
return not not lib |
end |
-- @method GetInstance |
-- @return The library with the given major/minor version. |
function AceLibrary:GetInstance(major, minor) |
argCheck(self, major, 2, "string") |
TryToLoadStandalone(major) |
if minor ~= false then |
TryToLoadStandalone(major) |
end |
local data = self.libs[major] |
local data, ver = LibStub:GetLibrary(major, true) |
if not data then |
_G.error(("Cannot find a library instance of %s."):format(major), 2) |
return |
if self.libs[major] then |
data, ver = self.libs[major].instance, self.libs[major].minor |
else |
_G.error(("Cannot find a library instance of %s."):format(major), 2) |
return |
end |
end |
if minor then |
if type(minor) == "string" then |
end |
end |
argCheck(self, minor, 2, "number") |
if data.minor ~= minor then |
if ver ~= minor then |
_G.error(("Cannot find a library instance of %s, minor version %d."):format(major, minor), 2) |
end |
end |
return data.instance |
return data |
end |
-- Syntax sugar. AceLibrary("FooBar-1.0") |
local AceEvent |
local tmp = {} |
-- @method Register |
-- @brief Registers a new version of a given library. |
-- @param newInstance the library to register |
local data = self.libs[major] |
if not data then |
-- This is new |
local instance = copyTable(newInstance) |
if LibStub:GetLibrary(major, true) then |
error(self, "Cannot register library %q. It is already registered with LibStub.", major) |
end |
local instance = LibStub:NewLibrary(major, minor) |
copyTable(newInstance, instance) |
crawlReplace(instance, instance, newInstance) |
destroyTable(newInstance) |
if AceLibrary == newInstance then |
if not rawget(instance, 'error') then |
rawset(instance, 'error', error) |
end |
if not WoW22 and not rawget(instance, 'assert') then |
rawset(instance, 'assert', assert) |
end |
if not rawget(instance, 'argCheck') then |
rawset(instance, 'argCheck', argCheck) |
end |
addToPositions(instance, major) |
if activateFunc then |
safecall(activateFunc, instance, nil, nil) -- no old version, so explicit nil |
--[[ if major ~= ACELIBRARY_MAJOR then |
for k,v in pairs(_G) do |
if v == instance then |
geterrorhandler()((debugstack():match("(.-: )in.-\n") or "") .. ("Cannot register library %q. It is part of the global table in _G[%q]."):format(major, k)) |
end |
end |
end]] |
end |
if externalFunc then |
for k,data in pairs(self.libs) do |
for k, data_instance in LibStub:IterateLibraries() do -- all libraries |
tmp[k] = data_instance |
end |
for k, data in pairs(self.libs) do -- Ace libraries which may not have been registered with LibStub |
tmp[k] = data.instance |
end |
for k, data_instance in pairs(tmp) do |
if k ~= major then |
safecall(externalFunc, instance, k, data.instance) |
safecall(externalFunc, instance, k, data_instance) |
end |
tmp[k] = nil |
end |
end |
for k,data in pairs(self.libs) do |
for k,data in pairs(self.libs) do -- only Ace libraries |
if k ~= major and data.externalFunc then |
safecall(data.externalFunc, data.instance, major, instance) |
end |
return instance |
end |
local instance = data.instance |
if minor <= data.minor then |
-- This one is already obsolete, raise an error. |
_G.error(("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"):format(major, data.minor, minor, major, minor), 2) |
return |
end |
local instance = data.instance |
-- This is an update |
local oldInstance = {} |
local libStubInstance = LibStub:GetLibrary(major, true) |
if not libStubInstance then -- non-LibStub AceLibrary registered the library |
-- pass |
elseif libStubInstance ~= instance then |
error(self, "Cannot register library %q. It is already registered with LibStub.", major) |
else |
LibStub:NewLibrary(major, minor) -- upgrade the minor version |
end |
addToPositions(newInstance, major) |
local isAceLibrary = (AceLibrary == newInstance) |
local old_error, old_assert, old_argCheck, old_pcall |
local old_error, old_argCheck, old_pcall |
if isAceLibrary then |
self = instance |
AceLibrary = instance |
old_error = instance.error |
if not WoW22 then |
old_assert = instance.assert |
end |
old_argCheck = instance.argCheck |
old_pcall = instance.pcall |
self.error = error |
if not WoW22 then |
self.assert = assert |
end |
self.argCheck = argCheck |
self.pcall = pcall |
end |
data.minor = minor |
data.deactivateFunc = deactivateFunc |
data.externalFunc = externalFunc |
rawset(instance, 'GetLibraryVersion', function(self) |
rawset(instance, 'GetLibraryVersion', function() |
return major, minor |
end) |
if not rawget(instance, 'error') then |
rawset(instance, 'error', error) |
end |
if not WoW22 and not rawget(instance, 'assert') then |
rawset(instance, 'assert', assert) |
end |
if not rawget(instance, 'argCheck') then |
rawset(instance, 'argCheck', argCheck) |
end |
if not rawget(i, 'error') or i.error == old_error then |
rawset(i, 'error', error) |
end |
if not WoW22 and (not rawget(i, 'assert') or i.assert == old_assert) then |
rawset(i, 'assert', assert) |
end |
if not rawget(i, 'argCheck') or i.argCheck == old_argCheck then |
rawset(i, 'argCheck', argCheck) |
end |
end |
end |
if activateFunc then |
safecall(activateFunc, instance, oldInstance, oldDeactivateFunc) |
--[[ if major ~= ACELIBRARY_MAJOR then |
for k,v in pairs(_G) do |
if v == instance then |
geterrorhandler()((debugstack():match("(.-: )in.-\n") or "") .. ("Cannot register library %q. It is part of the global table in _G[%q]."):format(major, k)) |
end |
end |
end]] |
safecall(activateFunc, instance, oldInstance, oldDeactivateFunc) |
else |
safecall(oldDeactivateFunc, oldInstance) |
end |
oldInstance = nil |
if externalFunc then |
for k,data in pairs(self.libs) do |
for k, data_instance in LibStub:IterateLibraries() do -- all libraries |
tmp[k] = data_instance |
end |
for k, data in pairs(self.libs) do -- Ace libraries which may not have been registered with LibStub |
tmp[k] = data.instance |
end |
for k, data_instance in pairs(tmp) do |
if k ~= major then |
safecall(externalFunc, instance, k, data.instance) |
safecall(externalFunc, instance, k, data_instance) |
end |
tmp[k] = nil |
end |
end |
return instance |
end |
local iter |
function AceLibrary:IterateLibraries() |
if not iter then |
local function iter(t, k) |
k = next(t, k) |
if not k then |
return nil |
else |
return k, t[k].instance |
end |
local t = {} |
for major, instance in LibStub:IterateLibraries() do |
t[major] = instance |
end |
for major, data in pairs(self.libs) do |
t[major] = data.instance |
end |
return pairs(t) |
end |
local function manuallyFinalize(major, instance) |
if AceLibrary.libs[major] then |
-- don't work on Ace libraries |
return |
end |
local finalizedExternalLibs = AceLibrary.finalizedExternalLibs |
if finalizedExternalLibs[major] then |
return |
end |
finalizedExternalLibs[major] = true |
for k,data in pairs(AceLibrary.libs) do -- only Ace libraries |
if k ~= major and data.externalFunc then |
safecall(data.externalFunc, data.instance, major, instance) |
end |
end |
return iter, self.libs, nil |
end |
-- @function Activate |
if not self.positions then |
self.positions = oldLib and oldLib.positions or setmetatable({}, { __mode = "k" }) |
end |
self.finalizedExternalLibs = oldLib and oldLib.finalizedExternalLibs or {} |
self.frame = oldLib and oldLib.frame or CreateFrame("Frame") |
self.frame:UnregisterAllEvents() |
self.frame:RegisterEvent("ADDON_LOADED") |
self.frame:SetScript("OnEvent", function() |
for major, instance in LibStub:IterateLibraries() do |
manuallyFinalize(major, instance) |
end |
end) |
for major, instance in LibStub:IterateLibraries() do |
manuallyFinalize(major, instance) |
end |
-- Expose the library in the global environment |
_G[ACELIBRARY_MAJOR] = self |
previous.positions = setmetatable({}, { __mode = "k" }) |
end |
AceLibrary.positions = previous.positions |
AceLibrary:Register(AceLibrary, ACELIBRARY_MAJOR, ACELIBRARY_MINOR, activate) |
AceLibrary:Register(AceLibrary, ACELIBRARY_MAJOR, ACELIBRARY_MINOR, activate, nil) |
## Interface: 30000 |
## X-Curse-Packaged-Version: r1096 |
## X-Curse-Project-Name: Ace2 |
## X-Curse-Project-ID: ace2 |
## X-Curse-Repository-ID: wow/ace2/mainline |
## Title: Lib: AceLibrary |
## Notes: AddOn development framework |
## Author: Ace Development Team |
## X-Website: http://www.wowace.com |
## X-Category: Library |
## X-License: LGPL v2.1 + MIT for AceOO-2.0 |
AceLibrary.lua |