Mercurial > wow > reaction
view modules/ReAction_ConfigUI/lib/AceLibrary/AceLibrary.lua @ 28:21bcaf8215ff
- converted to Ace3
- rearranged file layout
- configGUI menus not working right now
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Mon, 17 Mar 2008 18:24:53 +0000 |
parents | |
children |
line wrap: on
line source
--[[ Name: AceLibrary Revision: $Rev: 49421 $ 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: 49421 $ Website: http://www.wowace.com/ Documentation: http://www.wowace.com/index.php/AceLibrary SVN: http://svn.wowace.com/root/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 proper error tools. It is handy because all the errors occur in the file that called it, not in the library file itself. Dependencies: None License: LGPL v2.1 ]] local ACELIBRARY_MAJOR = "AceLibrary" local ACELIBRARY_MINOR = "$Revision: 49421 $" 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 AceLibrary_mt = {} setmetatable(AceLibrary, AceLibrary_mt) local function error(self, message, ...) if type(self) ~= "table" then return _G.error(("Bad argument #1 to `error' (table expected, got %s)"):format(type(self)), 2) end local stack = debugstack() if not message then local second = stack:match("\n(.-)\n") message = "error raised! " .. second else local arg = { ... } -- not worried about table creation, as errors don't happen often for i = 1, #arg do arg[i] = tostring(arg[i]) end for i = 1, 10 do table.insert(arg, "nil") end message = message:format(unpack(arg)) end if getmetatable(self) and getmetatable(self).__tostring then message = ("%s: %s"):format(tostring(self), message) elseif type(rawget(self, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self:GetLibraryVersion()) then message = ("%s: %s"):format(self:GetLibraryVersion(), message) elseif type(rawget(self, 'class')) == "table" and type(rawget(self.class, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self.class:GetLibraryVersion()) then message = ("%s: %s"):format(self.class:GetLibraryVersion(), message) end local first = stack:gsub("\n.*", "") local file = first:gsub(".*\\(.*).lua:%d+: .*", "%1") file = file:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") local i = 0 for s in stack:gmatch("\n([^\n]*)") do i = i + 1 if not s:find(file .. "%.lua:%d+:") and not s:find("%(tail call%)") then file = s:gsub("^.*\\(.*).lua:%d+: .*", "%1") file = file:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") break end end local j = 0 for s in stack:gmatch("\n([^\n]*)") do j = j + 1 if j > i and not s:find(file .. "%.lua:%d+:") and not s:find("%(tail call%)") then return _G.error(message, j+1) end end 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:match("\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 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:match("`argCheck'.-([`<].-['>])") if not func then 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) elseif kind4 then return 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) elseif kind3 then return error(self, "Bad argument #%s to %s (%s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, arg) elseif kind2 then return error(self, "Bad argument #%s to %s (%s or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, arg) else return error(self, "Bad argument #%s to %s (%s expected, got %s)", tonumber(num) or 0/0, func, kind, arg) end end end local pcall do local function check(self, ret, ...) if not ret then local s = ... return error(self, (s:gsub(".-%.lua:%d-: ", ""))) else return ... end end function pcall(self, func, ...) return check(self, _G.pcall(func, ...)) end end local recurse = {} local function addToPositions(t, major) if not AceLibrary.positions[t] or AceLibrary.positions[t] == major then rawset(t, recurse, true) AceLibrary.positions[t] = major for k,v in pairs(t) do if type(v) == "table" and not rawget(v, recurse) then addToPositions(v, major) end if type(k) == "table" and not rawget(k, recurse) then addToPositions(k, major) end end local mt = getmetatable(t) if mt and not rawget(mt, recurse) then addToPositions(mt, major) end rawset(t, recurse, nil) end end local function svnRevisionToNumber(text) 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:match("^%$Revision: (%d+) %$$"))) elseif text:find("^%$Rev: (%d+) %$$") then return tonumber((text:match("^%$Rev: (%d+) %$$"))) elseif text:find("^%$LastChangedRevision: (%d+) %$$") then return tonumber((text:match("^%$LastChangedRevision: (%d+) %$$"))) end end return nil end local crawlReplace do local recurse = {} local function func(t, to, from) if recurse[t] then return end recurse[t] = true local mt = getmetatable(t) setmetatable(t, nil) rawset(t, to, rawget(t, from)) rawset(t, from, nil) for k,v in pairs(t) do if v == from then t[k] = to elseif type(v) == "table" then if not recurse[v] then func(v, to, from) end end if type(k) == "table" then if not recurse[k] then func(k, to, from) end end end setmetatable(t, mt) if mt then if mt == from then setmetatable(t, to) elseif not recurse[mt] then func(mt, to, from) end end end function crawlReplace(t, to, from) func(t, to, from) for k in pairs(recurse) do recurse[k] = nil end end end -- @function destroyTable -- @brief remove all the contents of a table -- @param t table to destroy local function destroyTable(t) setmetatable(t, nil) for k,v in pairs(t) do t[k] = nil end end local function isFrame(frame) return type(frame) == "table" and type(rawget(frame, 0)) == "userdata" and type(rawget(frame, 'IsFrameType')) == "function" and getmetatable(frame) and type(rawget(getmetatable(frame), '__index')) == "function" end -- @function copyTable -- @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, to) if not to then to = {} end for k,v in pairs(from) do to[k] = v end setmetatable(to, getmetatable(from)) return to end -- @function deepTransfer -- @brief Fully transfer all data, keeping proper previous table -- backreferences stable. -- @param to The table with which data is to be injected into -- @param from The table whose data will be injected into the first -- @param saveFields If available, a shallow copy of the basic data is saved -- in here. -- @param list The account of table references -- @param list2 The current status on which tables have been traversed. local deepTransfer do -- @function examine -- @brief Take account of all the table references to be shared -- between the to and from tables. -- @param to The table with which data is to be injected into -- @param from The table whose data will be injected into the first -- @param list An account of the table references local function examine(to, from, list, major) list[from] = to for k,v in pairs(from) do if rawget(to, k) and type(from[k]) == "table" and type(to[k]) == "table" and not list[from[k]] then if from[k] == to[k] then list[from[k]] = to[k] elseif AceLibrary.positions[from[v]] ~= major and AceLibrary.positions[from[v]] then list[from[k]] = from[k] elseif not list[from[k]] then examine(to[k], from[k], list, major) end end end return list end function deepTransfer(to, from, saveFields, major, list, list2) setmetatable(to, nil) if not list then list = {} list2 = {} examine(to, from, list, major) end list2[to] = to for k,v in pairs(to) do if type(rawget(from, k)) ~= "table" or type(v) ~= "table" or isFrame(v) then if saveFields then saveFields[k] = v end to[k] = nil elseif v ~= _G then if saveFields then saveFields[k] = copyTable(v) end end end for k in pairs(from) do if rawget(to, k) and to[k] ~= from[k] and AceLibrary.positions[to[k]] == major and from[k] ~= _G then if not list2[to[k]] then deepTransfer(to[k], from[k], nil, major, list, list2) end to[k] = list[to[k]] or list2[to[k]] else rawset(to, k, from[k]) end end setmetatable(to, getmetatable(from)) local mt = getmetatable(to) if mt then if list[mt] then setmetatable(to, list[mt]) elseif mt.__index and list[mt.__index] then mt.__index = list[mt.__index] end end destroyTable(from) 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 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 if AceLibrary.scannedlibs[major] then return end AceLibrary.scannedlibs[major] = true local name, _, _, enabled, loadable = GetAddOnInfo(major) loadable = (enabled and loadable) or TryToEnable(name) local loaded = false if loadable then loaded = true LoadAddOn(name) end 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 loaded = true LoadAddOn(name) end end end return loaded end -- @method IsNewVersion -- @brief Obtain whether the supplied version would be an upgrade to the -- current version. This allows for bypass code in library -- declaration. -- @param major A string representing the major version -- @param minor An integer or an svn revision string representing the minor version -- @return whether the supplied version would be newer than what is -- currently available. function AceLibrary:IsNewVersion(major, minor) argCheck(self, major, 2, "string") TryToLoadStandalone(major) if type(minor) == "string" then local m = svnRevisionToNumber(minor) if m then minor = m else _G.error(("Bad argument #3 to `IsNewVersion'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2) 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 end return data.minor < minor end -- @method HasInstance -- @brief Returns whether an instance exists. This allows for optional support of a library. -- @param major A string representing the major version. -- @param minor (optional) An integer or an svn revision string representing the minor version. -- @return Whether an instance exists. function AceLibrary:HasInstance(major, minor) argCheck(self, major, 2, "string") 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) if m then minor = m else _G.error(("Bad argument #3 to `HasInstance'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2) end end argCheck(self, minor, 3, "number") if not lib then return false end return ver == minor end return not not lib end -- @method GetInstance -- @brief Returns the library with the given major/minor version. -- @param major A string representing the major version. -- @param minor (optional) An integer or an svn revision string representing the minor version. -- @return The library with the given major/minor version. function AceLibrary:GetInstance(major, minor) argCheck(self, major, 2, "string") if minor ~= false then TryToLoadStandalone(major) end local data, ver = LibStub:GetLibrary(major, true) if not data then 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 local m = svnRevisionToNumber(minor) if m then minor = m else _G.error(("Bad argument #3 to `GetInstance'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2) end end argCheck(self, minor, 2, "number") if ver ~= minor then _G.error(("Cannot find a library instance of %s, minor version %d."):format(major, minor), 2) end end return data end -- Syntax sugar. AceLibrary("FooBar-1.0") AceLibrary_mt.__call = AceLibrary.GetInstance local donothing = function() end local AceEvent local tmp = {} -- @method Register -- @brief Registers a new version of a given library. -- @param newInstance the library to register -- @param major the major version of the library -- @param minor the minor version of the library -- @param activateFunc (optional) A function to be called when the library is -- fully activated. Takes the arguments -- (newInstance [, oldInstance, oldDeactivateFunc]). If -- oldInstance is given, you should probably call -- oldDeactivateFunc(oldInstance). -- @param deactivateFunc (optional) A function to be called by a newer library's -- activateFunc. -- @param externalFunc (optional) A function to be called whenever a new -- library is registered. function AceLibrary:Register(newInstance, major, minor, activateFunc, deactivateFunc, externalFunc) argCheck(self, newInstance, 2, "table") argCheck(self, major, 3, "string") if major ~= ACELIBRARY_MAJOR then for k,v in pairs(_G) do if v == newInstance 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 if major ~= ACELIBRARY_MAJOR and not major:find("^[%a%-][%a%d%-]*%-%d+%.%d+$") then _G.error(string.format("Bad argument #3 to `Register'. Must be in the form of \"Name-1.0\". %q is not appropriate", major), 2) end if type(minor) == "string" then local m = svnRevisionToNumber(minor) if m then minor = m else _G.error(("Bad argument #4 to `Register'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2) end end argCheck(self, minor, 4, "number") if math.floor(minor) ~= minor or minor < 0 then error(self, "Bad argument #4 to `Register' (integer >= 0 expected, got %s)", minor) end argCheck(self, activateFunc, 5, "function", "nil") argCheck(self, deactivateFunc, 6, "function", "nil") argCheck(self, externalFunc, 7, "function", "nil") if not deactivateFunc then deactivateFunc = donothing end local data = self.libs[major] if not data then -- This is new 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 self = instance AceLibrary = instance end self.libs[major] = { instance = instance, minor = minor, deactivateFunc = deactivateFunc, externalFunc = externalFunc, } rawset(instance, 'GetLibraryVersion', function(self) 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(instance, 'pcall') then rawset(instance, 'pcall', pcall) 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_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) end tmp[k] = nil end end 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 end if major == "AceEvent-2.0" then AceEvent = instance end if AceEvent then AceEvent.TriggerEvent(self, "AceLibrary_Register", major, instance) end return instance end 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 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 deepTransfer(instance, newInstance, oldInstance, major) crawlReplace(instance, instance, newInstance) local oldDeactivateFunc = data.deactivateFunc data.minor = minor data.deactivateFunc = deactivateFunc data.externalFunc = externalFunc 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(instance, 'pcall') then rawset(instance, 'pcall', pcall) end if isAceLibrary then for _,v in pairs(self.libs) do local i = type(v) == "table" and v.instance if type(i) == "table" then 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 if not rawget(i, 'pcall') or i.pcall == old_pcall then rawset(i, 'pcall', pcall) end 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]] else safecall(oldDeactivateFunc, oldInstance) end oldInstance = nil if externalFunc then 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) end tmp[k] = nil end end return instance end function AceLibrary:IterateLibraries() 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 end -- @function Activate -- @brief The activateFunc for AceLibrary itself. Called when -- AceLibrary properly registers. -- @param self Reference to AceLibrary -- @param oldLib (optional) Reference to an old version of AceLibrary -- @param oldDeactivate (optional) Function to deactivate the old lib local function activate(self, oldLib, oldDeactivate) AceLibrary = self if not self.libs then self.libs = oldLib and oldLib.libs or {} self.scannedlibs = oldLib and oldLib.scannedlibs or {} end 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 if oldDeactivate then oldDeactivate(oldLib) end end if not previous then previous = AceLibrary end if not previous.libs then previous.libs = {} end AceLibrary.libs = previous.libs if not previous.positions then previous.positions = setmetatable({}, { __mode = "k" }) end AceLibrary.positions = previous.positions AceLibrary:Register(AceLibrary, ACELIBRARY_MAJOR, ACELIBRARY_MINOR, activate, nil)