Add files via upload
This commit is contained in:
@@ -0,0 +1,642 @@
|
||||
--- **AceAddon-3.0** provides a template for creating addon objects.
|
||||
-- It'll provide you with a set of callback functions that allow you to simplify the loading
|
||||
-- process of your addon.\\
|
||||
-- Callbacks provided are:\\
|
||||
-- * **OnInitialize**, which is called directly after the addon is fully loaded.
|
||||
-- * **OnEnable** which gets called during the PLAYER_LOGIN event, when most of the data provided by the game is already present.
|
||||
-- * **OnDisable**, which is only called when your addon is manually being disabled.
|
||||
-- @usage
|
||||
-- -- A small (but complete) addon, that doesn't do anything,
|
||||
-- -- but shows usage of the callbacks.
|
||||
-- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
|
||||
--
|
||||
-- function MyAddon:OnInitialize()
|
||||
-- -- do init tasks here, like loading the Saved Variables,
|
||||
-- -- or setting up slash commands.
|
||||
-- end
|
||||
--
|
||||
-- function MyAddon:OnEnable()
|
||||
-- -- Do more initialization here, that really enables the use of your addon.
|
||||
-- -- Register Events, Hook functions, Create Frames, Get information from
|
||||
-- -- the game that wasn't available in OnInitialize
|
||||
-- end
|
||||
--
|
||||
-- function MyAddon:OnDisable()
|
||||
-- -- Unhook, Unregister Events, Hide frames that you created.
|
||||
-- -- You would probably only use an OnDisable if you want to
|
||||
-- -- build a "standby" mode, or be able to toggle modules on/off.
|
||||
-- end
|
||||
-- @class file
|
||||
-- @name AceAddon-3.0.lua
|
||||
-- @release $Id: AceAddon-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
|
||||
|
||||
local MAJOR, MINOR = "AceAddon-3.0", 5
|
||||
local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceAddon then return end -- No Upgrade needed.
|
||||
|
||||
AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame
|
||||
AceAddon.addons = AceAddon.addons or {} -- addons in general
|
||||
AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon.
|
||||
AceAddon.initializequeue = AceAddon.initializequeue or {} -- addons that are new and not initialized
|
||||
AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized and waiting to be enabled
|
||||
AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon
|
||||
|
||||
-- Lua APIs
|
||||
local tinsert, tconcat, tremove = table.insert, table.concat, table.remove
|
||||
local fmt, tostring = string.format, tostring
|
||||
local select, pairs, next, type, unpack = select, pairs, next, type, unpack
|
||||
local loadstring, assert, error = loadstring, assert, error
|
||||
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: LibStub, IsLoggedIn, geterrorhandler
|
||||
|
||||
--[[
|
||||
xpcall safecall implementation
|
||||
]]
|
||||
local xpcall = xpcall
|
||||
|
||||
local function errorhandler(err)
|
||||
return geterrorhandler()(err)
|
||||
end
|
||||
|
||||
local function CreateDispatcher(argCount)
|
||||
local code = [[
|
||||
local xpcall, eh = ...
|
||||
local method, ARGS
|
||||
local function call() return method(ARGS) end
|
||||
|
||||
local function dispatch(func, ...)
|
||||
method = func
|
||||
if not method then return end
|
||||
ARGS = ...
|
||||
return xpcall(call, eh)
|
||||
end
|
||||
|
||||
return dispatch
|
||||
]]
|
||||
|
||||
local ARGS = {}
|
||||
for i = 1, argCount do ARGS[i] = "arg"..i end
|
||||
code = code:gsub("ARGS", tconcat(ARGS, ", "))
|
||||
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
|
||||
end
|
||||
|
||||
local Dispatchers = setmetatable({}, {__index=function(self, argCount)
|
||||
local dispatcher = CreateDispatcher(argCount)
|
||||
rawset(self, argCount, dispatcher)
|
||||
return dispatcher
|
||||
end})
|
||||
Dispatchers[0] = function(func)
|
||||
return xpcall(func, errorhandler)
|
||||
end
|
||||
|
||||
local function safecall(func, ...)
|
||||
-- we check to see if the func is passed is actually a function here and don't error when it isn't
|
||||
-- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not
|
||||
-- present execution should continue without hinderance
|
||||
if type(func) == "function" then
|
||||
return Dispatchers[select('#', ...)](func, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- local functions that will be implemented further down
|
||||
local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype
|
||||
|
||||
-- used in the addon metatable
|
||||
local function addontostring( self ) return self.name end
|
||||
|
||||
--- Create a new AceAddon-3.0 addon.
|
||||
-- Any libraries you specified will be embeded, and the addon will be scheduled for
|
||||
-- its OnInitialize and OnEnable callbacks.
|
||||
-- The final addon object, with all libraries embeded, will be returned.
|
||||
-- @paramsig [object ,]name[, lib, ...]
|
||||
-- @param object Table to use as a base for the addon (optional)
|
||||
-- @param name Name of the addon object to create
|
||||
-- @param lib List of libraries to embed into the addon
|
||||
-- @usage
|
||||
-- -- Create a simple addon object
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0")
|
||||
--
|
||||
-- -- Create a Addon object based on the table of a frame
|
||||
-- local MyFrame = CreateFrame("Frame")
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon(MyFrame, "MyAddon", "AceEvent-3.0")
|
||||
function AceAddon:NewAddon(objectorname, ...)
|
||||
local object,name
|
||||
local i=1
|
||||
if type(objectorname)=="table" then
|
||||
object=objectorname
|
||||
name=...
|
||||
i=2
|
||||
else
|
||||
name=objectorname
|
||||
end
|
||||
if type(name)~="string" then
|
||||
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2)
|
||||
end
|
||||
if self.addons[name] then
|
||||
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2)
|
||||
end
|
||||
|
||||
object = object or {}
|
||||
object.name = name
|
||||
|
||||
local addonmeta = {}
|
||||
local oldmeta = getmetatable(object)
|
||||
if oldmeta then
|
||||
for k, v in pairs(oldmeta) do addonmeta[k] = v end
|
||||
end
|
||||
addonmeta.__tostring = addontostring
|
||||
|
||||
setmetatable( object, addonmeta )
|
||||
self.addons[name] = object
|
||||
object.modules = {}
|
||||
object.defaultModuleLibraries = {}
|
||||
Embed( object ) -- embed NewModule, GetModule methods
|
||||
self:EmbedLibraries(object, select(i,...))
|
||||
|
||||
-- add to queue of addons to be initialized upon ADDON_LOADED
|
||||
tinsert(self.initializequeue, object)
|
||||
return object
|
||||
end
|
||||
|
||||
|
||||
--- Get the addon object by its name from the internal AceAddon registry.
|
||||
-- Throws an error if the addon object cannot be found (except if silent is set).
|
||||
-- @param name unique name of the addon object
|
||||
-- @param silent if true, the addon is optional, silently return nil if its not found
|
||||
-- @usage
|
||||
-- -- Get the Addon
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||
function AceAddon:GetAddon(name, silent)
|
||||
if not silent and not self.addons[name] then
|
||||
error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2)
|
||||
end
|
||||
return self.addons[name]
|
||||
end
|
||||
|
||||
-- - Embed a list of libraries into the specified addon.
|
||||
-- This function will try to embed all of the listed libraries into the addon
|
||||
-- and error if a single one fails.
|
||||
--
|
||||
-- **Note:** This function is for internal use by :NewAddon/:NewModule
|
||||
-- @paramsig addon, [lib, ...]
|
||||
-- @param addon addon object to embed the libs in
|
||||
-- @param lib List of libraries to embed into the addon
|
||||
function AceAddon:EmbedLibraries(addon, ...)
|
||||
for i=1,select("#", ... ) do
|
||||
local libname = select(i, ...)
|
||||
self:EmbedLibrary(addon, libname, false, 4)
|
||||
end
|
||||
end
|
||||
|
||||
-- - Embed a library into the addon object.
|
||||
-- This function will check if the specified library is registered with LibStub
|
||||
-- and if it has a :Embed function to call. It'll error if any of those conditions
|
||||
-- fails.
|
||||
--
|
||||
-- **Note:** This function is for internal use by :EmbedLibraries
|
||||
-- @paramsig addon, libname[, silent[, offset]]
|
||||
-- @param addon addon object to embed the library in
|
||||
-- @param libname name of the library to embed
|
||||
-- @param silent marks an embed to fail silently if the library doesn't exist (optional)
|
||||
-- @param offset will push the error messages back to said offset, defaults to 2 (optional)
|
||||
function AceAddon:EmbedLibrary(addon, libname, silent, offset)
|
||||
local lib = LibStub:GetLibrary(libname, true)
|
||||
if not lib and not silent then
|
||||
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2)
|
||||
elseif lib and type(lib.Embed) == "function" then
|
||||
lib:Embed(addon)
|
||||
tinsert(self.embeds[addon], libname)
|
||||
return true
|
||||
elseif lib then
|
||||
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2)
|
||||
end
|
||||
end
|
||||
|
||||
--- Return the specified module from an addon object.
|
||||
-- Throws an error if the addon object cannot be found (except if silent is set)
|
||||
-- @name //addon//:GetModule
|
||||
-- @paramsig name[, silent]
|
||||
-- @param name unique name of the module
|
||||
-- @param silent if true, the module is optional, silently return nil if its not found (optional)
|
||||
-- @usage
|
||||
-- -- Get the Addon
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||
-- -- Get the Module
|
||||
-- MyModule = MyAddon:GetModule("MyModule")
|
||||
function GetModule(self, name, silent)
|
||||
if not self.modules[name] and not silent then
|
||||
error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2)
|
||||
end
|
||||
return self.modules[name]
|
||||
end
|
||||
|
||||
local function IsModuleTrue(self) return true end
|
||||
|
||||
--- Create a new module for the addon.
|
||||
-- The new module can have its own embeded libraries and/or use a module prototype to be mixed into the module.\\
|
||||
-- A module has the same functionality as a real addon, it can have modules of its own, and has the same API as
|
||||
-- an addon object.
|
||||
-- @name //addon//:NewModule
|
||||
-- @paramsig name[, prototype|lib[, lib, ...]]
|
||||
-- @param name unique name of the module
|
||||
-- @param prototype object to derive this module from, methods and values from this table will be mixed into the module (optional)
|
||||
-- @param lib List of libraries to embed into the addon
|
||||
-- @usage
|
||||
-- -- Create a module with some embeded libraries
|
||||
-- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0")
|
||||
--
|
||||
-- -- Create a module with a prototype
|
||||
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
|
||||
-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0")
|
||||
function NewModule(self, name, prototype, ...)
|
||||
if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end
|
||||
if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end
|
||||
|
||||
if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end
|
||||
|
||||
-- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well.
|
||||
-- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is.
|
||||
local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name))
|
||||
|
||||
module.IsModule = IsModuleTrue
|
||||
module:SetEnabledState(self.defaultModuleState)
|
||||
module.moduleName = name
|
||||
|
||||
if type(prototype) == "string" then
|
||||
AceAddon:EmbedLibraries(module, prototype, ...)
|
||||
else
|
||||
AceAddon:EmbedLibraries(module, ...)
|
||||
end
|
||||
AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries))
|
||||
|
||||
if not prototype or type(prototype) == "string" then
|
||||
prototype = self.defaultModulePrototype or nil
|
||||
end
|
||||
|
||||
if type(prototype) == "table" then
|
||||
local mt = getmetatable(module)
|
||||
mt.__index = prototype
|
||||
setmetatable(module, mt) -- More of a Base class type feel.
|
||||
end
|
||||
|
||||
safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy.
|
||||
self.modules[name] = module
|
||||
|
||||
return module
|
||||
end
|
||||
|
||||
--- Returns the real name of the addon or module, without any prefix.
|
||||
-- @name //addon//:GetName
|
||||
-- @paramsig
|
||||
-- @usage
|
||||
-- print(MyAddon:GetName())
|
||||
-- -- prints "MyAddon"
|
||||
function GetName(self)
|
||||
return self.moduleName or self.name
|
||||
end
|
||||
|
||||
--- Enables the Addon, if possible, return true or false depending on success.
|
||||
-- This internally calls AceAddon:EnableAddon(), thus dispatching a OnEnable callback
|
||||
-- and enabling all modules of the addon (unless explicitly disabled).\\
|
||||
-- :Enable() also sets the internal `enableState` variable to true
|
||||
-- @name //addon//:Enable
|
||||
-- @paramsig
|
||||
-- @usage
|
||||
-- -- Enable MyModule
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||
-- MyModule = MyAddon:GetModule("MyModule")
|
||||
-- MyModule:Enable()
|
||||
function Enable(self)
|
||||
self:SetEnabledState(true)
|
||||
return AceAddon:EnableAddon(self)
|
||||
end
|
||||
|
||||
--- Disables the Addon, if possible, return true or false depending on success.
|
||||
-- This internally calls AceAddon:DisableAddon(), thus dispatching a OnDisable callback
|
||||
-- and disabling all modules of the addon.\\
|
||||
-- :Disable() also sets the internal `enableState` variable to false
|
||||
-- @name //addon//:Disable
|
||||
-- @paramsig
|
||||
-- @usage
|
||||
-- -- Disable MyAddon
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||
-- MyAddon:Disable()
|
||||
function Disable(self)
|
||||
self:SetEnabledState(false)
|
||||
return AceAddon:DisableAddon(self)
|
||||
end
|
||||
|
||||
--- Enables the Module, if possible, return true or false depending on success.
|
||||
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object.
|
||||
-- @name //addon//:EnableModule
|
||||
-- @paramsig name
|
||||
-- @usage
|
||||
-- -- Enable MyModule using :GetModule
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||
-- MyModule = MyAddon:GetModule("MyModule")
|
||||
-- MyModule:Enable()
|
||||
--
|
||||
-- -- Enable MyModule using the short-hand
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||
-- MyAddon:EnableModule("MyModule")
|
||||
function EnableModule(self, name)
|
||||
local module = self:GetModule( name )
|
||||
return module:Enable()
|
||||
end
|
||||
|
||||
--- Disables the Module, if possible, return true or false depending on success.
|
||||
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object.
|
||||
-- @name //addon//:DisableModule
|
||||
-- @paramsig name
|
||||
-- @usage
|
||||
-- -- Disable MyModule using :GetModule
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||
-- MyModule = MyAddon:GetModule("MyModule")
|
||||
-- MyModule:Disable()
|
||||
--
|
||||
-- -- Disable MyModule using the short-hand
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
|
||||
-- MyAddon:DisableModule("MyModule")
|
||||
function DisableModule(self, name)
|
||||
local module = self:GetModule( name )
|
||||
return module:Disable()
|
||||
end
|
||||
|
||||
--- Set the default libraries to be mixed into all modules created by this object.
|
||||
-- Note that you can only change the default module libraries before any module is created.
|
||||
-- @name //addon//:SetDefaultModuleLibraries
|
||||
-- @paramsig lib[, lib, ...]
|
||||
-- @param lib List of libraries to embed into the addon
|
||||
-- @usage
|
||||
-- -- Create the addon object
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
|
||||
-- -- Configure default libraries for modules (all modules need AceEvent-3.0)
|
||||
-- MyAddon:SetDefaultModuleLibraries("AceEvent-3.0")
|
||||
-- -- Create a module
|
||||
-- MyModule = MyAddon:NewModule("MyModule")
|
||||
function SetDefaultModuleLibraries(self, ...)
|
||||
if next(self.modules) then
|
||||
error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2)
|
||||
end
|
||||
self.defaultModuleLibraries = {...}
|
||||
end
|
||||
|
||||
--- Set the default state in which new modules are being created.
|
||||
-- Note that you can only change the default state before any module is created.
|
||||
-- @name //addon//:SetDefaultModuleState
|
||||
-- @paramsig state
|
||||
-- @param state Default state for new modules, true for enabled, false for disabled
|
||||
-- @usage
|
||||
-- -- Create the addon object
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon")
|
||||
-- -- Set the default state to "disabled"
|
||||
-- MyAddon:SetDefaultModuleState(false)
|
||||
-- -- Create a module and explicilty enable it
|
||||
-- MyModule = MyAddon:NewModule("MyModule")
|
||||
-- MyModule:Enable()
|
||||
function SetDefaultModuleState(self, state)
|
||||
if next(self.modules) then
|
||||
error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2)
|
||||
end
|
||||
self.defaultModuleState = state
|
||||
end
|
||||
|
||||
--- Set the default prototype to use for new modules on creation.
|
||||
-- Note that you can only change the default prototype before any module is created.
|
||||
-- @name //addon//:SetDefaultModulePrototype
|
||||
-- @paramsig prototype
|
||||
-- @param prototype Default prototype for the new modules (table)
|
||||
-- @usage
|
||||
-- -- Define a prototype
|
||||
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
|
||||
-- -- Set the default prototype
|
||||
-- MyAddon:SetDefaultModulePrototype(prototype)
|
||||
-- -- Create a module and explicitly Enable it
|
||||
-- MyModule = MyAddon:NewModule("MyModule")
|
||||
-- MyModule:Enable()
|
||||
-- -- should print "OnEnable called!" now
|
||||
-- @see NewModule
|
||||
function SetDefaultModulePrototype(self, prototype)
|
||||
if next(self.modules) then
|
||||
error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2)
|
||||
end
|
||||
if type(prototype) ~= "table" then
|
||||
error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2)
|
||||
end
|
||||
self.defaultModulePrototype = prototype
|
||||
end
|
||||
|
||||
--- Set the state of an addon or module
|
||||
-- This should only be called before any enabling actually happend, e.g. in/before OnInitialize.
|
||||
-- @name //addon//:SetEnabledState
|
||||
-- @paramsig state
|
||||
-- @param state the state of an addon or module (enabled=true, disabled=false)
|
||||
function SetEnabledState(self, state)
|
||||
self.enabledState = state
|
||||
end
|
||||
|
||||
|
||||
--- Return an iterator of all modules associated to the addon.
|
||||
-- @name //addon//:IterateModules
|
||||
-- @paramsig
|
||||
-- @usage
|
||||
-- -- Enable all modules
|
||||
-- for name, module in MyAddon:IterateModules() do
|
||||
-- module:Enable()
|
||||
-- end
|
||||
local function IterateModules(self) return pairs(self.modules) end
|
||||
|
||||
-- Returns an iterator of all embeds in the addon
|
||||
-- @name //addon//:IterateEmbeds
|
||||
-- @paramsig
|
||||
local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end
|
||||
|
||||
--- Query the enabledState of an addon.
|
||||
-- @name //addon//:IsEnabled
|
||||
-- @paramsig
|
||||
-- @usage
|
||||
-- if MyAddon:IsEnabled() then
|
||||
-- MyAddon:Disable()
|
||||
-- end
|
||||
local function IsEnabled(self) return self.enabledState end
|
||||
local mixins = {
|
||||
NewModule = NewModule,
|
||||
GetModule = GetModule,
|
||||
Enable = Enable,
|
||||
Disable = Disable,
|
||||
EnableModule = EnableModule,
|
||||
DisableModule = DisableModule,
|
||||
IsEnabled = IsEnabled,
|
||||
SetDefaultModuleLibraries = SetDefaultModuleLibraries,
|
||||
SetDefaultModuleState = SetDefaultModuleState,
|
||||
SetDefaultModulePrototype = SetDefaultModulePrototype,
|
||||
SetEnabledState = SetEnabledState,
|
||||
IterateModules = IterateModules,
|
||||
IterateEmbeds = IterateEmbeds,
|
||||
GetName = GetName,
|
||||
}
|
||||
local function IsModule(self) return false end
|
||||
local pmixins = {
|
||||
defaultModuleState = true,
|
||||
enabledState = true,
|
||||
IsModule = IsModule,
|
||||
}
|
||||
-- Embed( target )
|
||||
-- target (object) - target object to embed aceaddon in
|
||||
--
|
||||
-- this is a local function specifically since it's meant to be only called internally
|
||||
function Embed(target)
|
||||
for k, v in pairs(mixins) do
|
||||
target[k] = v
|
||||
end
|
||||
for k, v in pairs(pmixins) do
|
||||
target[k] = target[k] or v
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- - Initialize the addon after creation.
|
||||
-- This function is only used internally during the ADDON_LOADED event
|
||||
-- It will call the **OnInitialize** function on the addon object (if present),
|
||||
-- and the **OnEmbedInitialize** function on all embeded libraries.
|
||||
--
|
||||
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
|
||||
-- @param addon addon object to intialize
|
||||
function AceAddon:InitializeAddon(addon)
|
||||
safecall(addon.OnInitialize, addon)
|
||||
|
||||
local embeds = self.embeds[addon]
|
||||
for i = 1, #embeds do
|
||||
local lib = LibStub:GetLibrary(embeds[i], true)
|
||||
if lib then safecall(lib.OnEmbedInitialize, lib, addon) end
|
||||
end
|
||||
|
||||
-- we don't call InitializeAddon on modules specifically, this is handled
|
||||
-- from the event handler and only done _once_
|
||||
end
|
||||
|
||||
-- - Enable the addon after creation.
|
||||
-- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED,
|
||||
-- if IsLoggedIn() already returns true at that point, e.g. for LoD Addons.
|
||||
-- It will call the **OnEnable** function on the addon object (if present),
|
||||
-- and the **OnEmbedEnable** function on all embeded libraries.\\
|
||||
-- This function does not toggle the enable state of the addon itself, and will return early if the addon is disabled.
|
||||
--
|
||||
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
|
||||
-- Use :Enable on the addon itself instead.
|
||||
-- @param addon addon object to enable
|
||||
function AceAddon:EnableAddon(addon)
|
||||
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end
|
||||
if self.statuses[addon.name] or not addon.enabledState then return false end
|
||||
|
||||
-- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable.
|
||||
self.statuses[addon.name] = true
|
||||
|
||||
safecall(addon.OnEnable, addon)
|
||||
|
||||
-- make sure we're still enabled before continueing
|
||||
if self.statuses[addon.name] then
|
||||
local embeds = self.embeds[addon]
|
||||
for i = 1, #embeds do
|
||||
local lib = LibStub:GetLibrary(embeds[i], true)
|
||||
if lib then safecall(lib.OnEmbedEnable, lib, addon) end
|
||||
end
|
||||
|
||||
-- enable possible modules.
|
||||
for name, module in pairs(addon.modules) do
|
||||
self:EnableAddon(module)
|
||||
end
|
||||
end
|
||||
return self.statuses[addon.name] -- return true if we're disabled
|
||||
end
|
||||
|
||||
-- - Disable the addon
|
||||
-- Note: This function is only used internally.
|
||||
-- It will call the **OnDisable** function on the addon object (if present),
|
||||
-- and the **OnEmbedDisable** function on all embeded libraries.\\
|
||||
-- This function does not toggle the enable state of the addon itself, and will return early if the addon is still enabled.
|
||||
--
|
||||
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
|
||||
-- Use :Disable on the addon itself instead.
|
||||
-- @param addon addon object to enable
|
||||
function AceAddon:DisableAddon(addon)
|
||||
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end
|
||||
if not self.statuses[addon.name] then return false end
|
||||
|
||||
-- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable.
|
||||
self.statuses[addon.name] = false
|
||||
|
||||
safecall( addon.OnDisable, addon )
|
||||
|
||||
-- make sure we're still disabling...
|
||||
if not self.statuses[addon.name] then
|
||||
local embeds = self.embeds[addon]
|
||||
for i = 1, #embeds do
|
||||
local lib = LibStub:GetLibrary(embeds[i], true)
|
||||
if lib then safecall(lib.OnEmbedDisable, lib, addon) end
|
||||
end
|
||||
-- disable possible modules.
|
||||
for name, module in pairs(addon.modules) do
|
||||
self:DisableAddon(module)
|
||||
end
|
||||
end
|
||||
|
||||
return not self.statuses[addon.name] -- return true if we're disabled
|
||||
end
|
||||
|
||||
--- Get an iterator over all registered addons.
|
||||
-- @usage
|
||||
-- -- Print a list of all installed AceAddon's
|
||||
-- for name, addon in AceAddon:IterateAddons() do
|
||||
-- print("Addon: " .. name)
|
||||
-- end
|
||||
function AceAddon:IterateAddons() return pairs(self.addons) end
|
||||
|
||||
--- Get an iterator over the internal status registry.
|
||||
-- @usage
|
||||
-- -- Print a list of all enabled addons
|
||||
-- for name, status in AceAddon:IterateAddonStatus() do
|
||||
-- if status then
|
||||
-- print("EnabledAddon: " .. name)
|
||||
-- end
|
||||
-- end
|
||||
function AceAddon:IterateAddonStatus() return pairs(self.statuses) end
|
||||
|
||||
-- Following Iterators are deprecated, and their addon specific versions should be used
|
||||
-- e.g. addon:IterateEmbeds() instead of :IterateEmbedsOnAddon(addon)
|
||||
function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end
|
||||
function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end
|
||||
|
||||
-- Event Handling
|
||||
local function onEvent(this, event, arg1)
|
||||
if event == "ADDON_LOADED" or event == "PLAYER_LOGIN" then
|
||||
-- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration
|
||||
while(#AceAddon.initializequeue > 0) do
|
||||
local addon = tremove(AceAddon.initializequeue, 1)
|
||||
-- this might be an issue with recursion - TODO: validate
|
||||
if event == "ADDON_LOADED" then addon.baseName = arg1 end
|
||||
AceAddon:InitializeAddon(addon)
|
||||
tinsert(AceAddon.enablequeue, addon)
|
||||
end
|
||||
|
||||
if IsLoggedIn() then
|
||||
while(#AceAddon.enablequeue > 0) do
|
||||
local addon = tremove(AceAddon.enablequeue, 1)
|
||||
AceAddon:EnableAddon(addon)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
AceAddon.frame:RegisterEvent("ADDON_LOADED")
|
||||
AceAddon.frame:RegisterEvent("PLAYER_LOGIN")
|
||||
AceAddon.frame:SetScript("OnEvent", onEvent)
|
||||
|
||||
-- upgrade embeded
|
||||
for name, addon in pairs(AceAddon.addons) do
|
||||
Embed(addon)
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceAddon-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -0,0 +1,309 @@
|
||||
--- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels.
|
||||
-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
|
||||
-- **ChatThrottleLib** is of course being used to avoid being disconnected by the server.
|
||||
--
|
||||
-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by
|
||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||
-- and can be accessed directly, without having to explicitly call AceComm itself.\\
|
||||
-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
|
||||
-- make into AceComm.
|
||||
-- @class file
|
||||
-- @name AceComm-3.0
|
||||
-- @release $Id: AceComm-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
|
||||
|
||||
--[[ AceComm-3.0
|
||||
|
||||
TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited.
|
||||
|
||||
]]
|
||||
|
||||
local MAJOR, MINOR = "AceComm-3.0", 6
|
||||
|
||||
local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceComm then return end
|
||||
|
||||
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
|
||||
local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
|
||||
|
||||
-- Lua APIs
|
||||
local type, next, pairs, tostring = type, next, pairs, tostring
|
||||
local strsub, strfind = string.sub, string.find
|
||||
local tinsert, tconcat = table.insert, table.concat
|
||||
local error, assert = error, assert
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler
|
||||
|
||||
AceComm.embeds = AceComm.embeds or {}
|
||||
|
||||
-- for my sanity and yours, let's give the message type bytes some names
|
||||
local MSG_MULTI_FIRST = "\001"
|
||||
local MSG_MULTI_NEXT = "\002"
|
||||
local MSG_MULTI_LAST = "\003"
|
||||
|
||||
AceComm.multipart_origprefixes = AceComm.multipart_origprefixes or {} -- e.g. "Prefix\001"="Prefix", "Prefix\002"="Prefix"
|
||||
AceComm.multipart_reassemblers = AceComm.multipart_reassemblers or {} -- e.g. "Prefix\001"="OnReceiveMultipartFirst"
|
||||
|
||||
-- the multipart message spool: indexed by a combination of sender+distribution+
|
||||
AceComm.multipart_spool = AceComm.multipart_spool or {}
|
||||
|
||||
--- Register for Addon Traffic on a specified prefix
|
||||
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
|
||||
-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
|
||||
function AceComm:RegisterComm(prefix, method)
|
||||
if method == nil then
|
||||
method = "OnCommReceived"
|
||||
end
|
||||
|
||||
return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
|
||||
end
|
||||
|
||||
local warnedPrefix=false
|
||||
|
||||
--- Send a message over the Addon Channel
|
||||
-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
|
||||
-- @param text Data to send, nils (\000) not allowed. Any length.
|
||||
-- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API
|
||||
-- @param target Destination for some distributions; see SendAddonMessage API
|
||||
-- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL".
|
||||
-- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send.
|
||||
-- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified.
|
||||
function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg)
|
||||
prio = prio or "NORMAL" -- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery!
|
||||
if not( type(prefix)=="string" and
|
||||
type(text)=="string" and
|
||||
type(distribution)=="string" and
|
||||
(target==nil or type(target)=="string") and
|
||||
(prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
|
||||
) then
|
||||
error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
|
||||
end
|
||||
|
||||
if strfind(prefix, "[\001-\009]") then
|
||||
if strfind(prefix, "[\001-\003]") then
|
||||
error("SendCommMessage: Characters \\001--\\003 in prefix are reserved for AceComm metadata", 2)
|
||||
elseif not warnedPrefix then
|
||||
-- I have some ideas about future extensions that require more control characters /mikk, 20090808
|
||||
geterrorhandler()("SendCommMessage: Heads-up developers: Characters \\004--\\009 in prefix are reserved for AceComm future extension")
|
||||
warnedPrefix = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local textlen = #text
|
||||
local maxtextlen = 254 - #prefix -- 254 is the max length of prefix + text that can be sent in one message
|
||||
local queueName = prefix..distribution..(target or "")
|
||||
|
||||
local ctlCallback = nil
|
||||
if callbackFn then
|
||||
ctlCallback = function(sent)
|
||||
return callbackFn(callbackArg, sent, textlen)
|
||||
end
|
||||
end
|
||||
|
||||
if textlen <= maxtextlen then
|
||||
-- fits all in one message
|
||||
CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
|
||||
else
|
||||
maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix
|
||||
|
||||
-- first part
|
||||
local chunk = strsub(text, 1, maxtextlen)
|
||||
CTL:SendAddonMessage(prio, prefix..MSG_MULTI_FIRST, chunk, distribution, target, queueName, ctlCallback, maxtextlen)
|
||||
|
||||
-- continuation
|
||||
local pos = 1+maxtextlen
|
||||
local prefix2 = prefix..MSG_MULTI_NEXT
|
||||
|
||||
while pos+maxtextlen <= textlen do
|
||||
chunk = strsub(text, pos, pos+maxtextlen-1)
|
||||
CTL:SendAddonMessage(prio, prefix2, chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
|
||||
pos = pos + maxtextlen
|
||||
end
|
||||
|
||||
-- final part
|
||||
chunk = strsub(text, pos)
|
||||
CTL:SendAddonMessage(prio, prefix..MSG_MULTI_LAST, chunk, distribution, target, queueName, ctlCallback, textlen)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
----------------------------------------
|
||||
-- Message receiving
|
||||
----------------------------------------
|
||||
|
||||
do
|
||||
local compost = setmetatable({}, {__mode = "k"})
|
||||
local function new()
|
||||
local t = next(compost)
|
||||
if t then
|
||||
compost[t]=nil
|
||||
for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
|
||||
t[i]=nil
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
return {}
|
||||
end
|
||||
|
||||
local function lostdatawarning(prefix,sender,where)
|
||||
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
|
||||
end
|
||||
|
||||
function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
|
||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||
local spool = AceComm.multipart_spool
|
||||
|
||||
--[[
|
||||
if spool[key] then
|
||||
lostdatawarning(prefix,sender,"First")
|
||||
-- continue and overwrite
|
||||
end
|
||||
--]]
|
||||
|
||||
spool[key] = message -- plain string for now
|
||||
end
|
||||
|
||||
function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender)
|
||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||
local spool = AceComm.multipart_spool
|
||||
local olddata = spool[key]
|
||||
|
||||
if not olddata then
|
||||
--lostdatawarning(prefix,sender,"Next")
|
||||
return
|
||||
end
|
||||
|
||||
if type(olddata)~="table" then
|
||||
-- ... but what we have is not a table. So make it one. (Pull a composted one if available)
|
||||
local t = new()
|
||||
t[1] = olddata -- add old data as first string
|
||||
t[2] = message -- and new message as second string
|
||||
spool[key] = t -- and put the table in the spool instead of the old string
|
||||
else
|
||||
tinsert(olddata, message)
|
||||
end
|
||||
end
|
||||
|
||||
function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender)
|
||||
local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
|
||||
local spool = AceComm.multipart_spool
|
||||
local olddata = spool[key]
|
||||
|
||||
if not olddata then
|
||||
--lostdatawarning(prefix,sender,"End")
|
||||
return
|
||||
end
|
||||
|
||||
spool[key] = nil
|
||||
|
||||
if type(olddata) == "table" then
|
||||
-- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
|
||||
tinsert(olddata, message)
|
||||
AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender)
|
||||
compost[olddata] = true
|
||||
else
|
||||
-- if we've only received a "first", the spooled data will still only be a string
|
||||
AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----------------------------------------
|
||||
-- Embed CallbackHandler
|
||||
----------------------------------------
|
||||
|
||||
if not AceComm.callbacks then
|
||||
-- ensure that 'prefix to watch' table is consistent with registered
|
||||
-- callbacks
|
||||
AceComm.__prefixes = {}
|
||||
|
||||
AceComm.callbacks = CallbackHandler:New(AceComm,
|
||||
"_RegisterComm",
|
||||
"UnregisterComm",
|
||||
"UnregisterAllComm")
|
||||
end
|
||||
|
||||
function AceComm.callbacks:OnUsed(target, prefix)
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = prefix
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = "OnReceiveMultipartFirst"
|
||||
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = prefix
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = "OnReceiveMultipartNext"
|
||||
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = prefix
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = "OnReceiveMultipartLast"
|
||||
end
|
||||
|
||||
function AceComm.callbacks:OnUnused(target, prefix)
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = nil
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = nil
|
||||
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = nil
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = nil
|
||||
|
||||
AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = nil
|
||||
AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = nil
|
||||
end
|
||||
|
||||
local function OnEvent(this, event, ...)
|
||||
if event == "CHAT_MSG_ADDON" then
|
||||
local prefix,message,distribution,sender = ...
|
||||
local reassemblername = AceComm.multipart_reassemblers[prefix]
|
||||
if reassemblername then
|
||||
-- multipart: reassemble
|
||||
local aceCommReassemblerFunc = AceComm[reassemblername]
|
||||
local origprefix = AceComm.multipart_origprefixes[prefix]
|
||||
aceCommReassemblerFunc(AceComm, origprefix, message, distribution, sender)
|
||||
else
|
||||
-- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
|
||||
AceComm.callbacks:Fire(prefix, message, distribution, sender)
|
||||
end
|
||||
else
|
||||
assert(false, "Received "..tostring(event).." event?!")
|
||||
end
|
||||
end
|
||||
|
||||
AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame")
|
||||
AceComm.frame:SetScript("OnEvent", OnEvent)
|
||||
AceComm.frame:UnregisterAllEvents()
|
||||
AceComm.frame:RegisterEvent("CHAT_MSG_ADDON")
|
||||
|
||||
|
||||
----------------------------------------
|
||||
-- Base library stuff
|
||||
----------------------------------------
|
||||
|
||||
local mixins = {
|
||||
"RegisterComm",
|
||||
"UnregisterComm",
|
||||
"UnregisterAllComm",
|
||||
"SendCommMessage",
|
||||
}
|
||||
|
||||
-- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:..
|
||||
-- @param target target object to embed AceComm-3.0 in
|
||||
function AceComm:Embed(target)
|
||||
for k, v in pairs(mixins) do
|
||||
target[v] = self[v]
|
||||
end
|
||||
self.embeds[target] = true
|
||||
return target
|
||||
end
|
||||
|
||||
function AceComm:OnEmbedDisable(target)
|
||||
target:UnregisterAllComm()
|
||||
end
|
||||
|
||||
-- Update embeds
|
||||
for target, v in pairs(AceComm.embeds) do
|
||||
AceComm:Embed(target)
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="ChatThrottleLib.lua"/>
|
||||
<Script file="AceComm-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -0,0 +1,503 @@
|
||||
--
|
||||
-- ChatThrottleLib by Mikk
|
||||
--
|
||||
-- Manages AddOn chat output to keep player from getting kicked off.
|
||||
--
|
||||
-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept
|
||||
-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage.
|
||||
--
|
||||
-- Priorities get an equal share of available bandwidth when fully loaded.
|
||||
-- Communication channels are separated on extension+chattype+destination and
|
||||
-- get round-robinned. (Destination only matters for whispers and channels,
|
||||
-- obviously)
|
||||
--
|
||||
-- Will install hooks for SendChatMessage and SendAddonMessage to measure
|
||||
-- bandwidth bypassing the library and use less bandwidth itself.
|
||||
--
|
||||
--
|
||||
-- Fully embeddable library. Just copy this file into your addon directory,
|
||||
-- add it to the .toc, and it's done.
|
||||
--
|
||||
-- Can run as a standalone addon also, but, really, just embed it! :-)
|
||||
--
|
||||
|
||||
local CTL_VERSION = 21
|
||||
|
||||
local _G = _G
|
||||
|
||||
if _G.ChatThrottleLib then
|
||||
if _G.ChatThrottleLib.version >= CTL_VERSION then
|
||||
-- There's already a newer (or same) version loaded. Buh-bye.
|
||||
return
|
||||
elseif not _G.ChatThrottleLib.securelyHooked then
|
||||
print("ChatThrottleLib: Warning: There's an ANCIENT ChatThrottleLib.lua (pre-wow 2.0, <v16) in an addon somewhere. Get the addon updated or copy in a newer ChatThrottleLib.lua (>=v16) in it!")
|
||||
-- ATTEMPT to unhook; this'll behave badly if someone else has hooked...
|
||||
-- ... and if someone has securehooked, they can kiss that goodbye too... >.<
|
||||
_G.SendChatMessage = _G.ChatThrottleLib.ORIG_SendChatMessage
|
||||
if _G.ChatThrottleLib.ORIG_SendAddonMessage then
|
||||
_G.SendAddonMessage = _G.ChatThrottleLib.ORIG_SendAddonMessage
|
||||
end
|
||||
end
|
||||
_G.ChatThrottleLib.ORIG_SendChatMessage = nil
|
||||
_G.ChatThrottleLib.ORIG_SendAddonMessage = nil
|
||||
end
|
||||
|
||||
if not _G.ChatThrottleLib then
|
||||
_G.ChatThrottleLib = {}
|
||||
end
|
||||
|
||||
ChatThrottleLib = _G.ChatThrottleLib -- in case some addon does "local ChatThrottleLib" above us and we're copypasted (AceComm-2, sigh)
|
||||
local ChatThrottleLib = _G.ChatThrottleLib
|
||||
|
||||
ChatThrottleLib.version = CTL_VERSION
|
||||
|
||||
|
||||
|
||||
------------------ TWEAKABLES -----------------
|
||||
|
||||
ChatThrottleLib.MAX_CPS = 800 -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800.
|
||||
ChatThrottleLib.MSG_OVERHEAD = 40 -- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff
|
||||
|
||||
ChatThrottleLib.BURST = 4000 -- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now.
|
||||
|
||||
ChatThrottleLib.MIN_FPS = 20 -- Reduce output CPS to half (and don't burst) if FPS drops below this value
|
||||
|
||||
|
||||
local setmetatable = setmetatable
|
||||
local table_remove = table.remove
|
||||
local tostring = tostring
|
||||
local GetTime = GetTime
|
||||
local math_min = math.min
|
||||
local math_max = math.max
|
||||
local next = next
|
||||
local strlen = string.len
|
||||
local GetFrameRate = GetFrameRate
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- Double-linked ring implementation
|
||||
|
||||
local Ring = {}
|
||||
local RingMeta = { __index = Ring }
|
||||
|
||||
function Ring:New()
|
||||
local ret = {}
|
||||
setmetatable(ret, RingMeta)
|
||||
return ret
|
||||
end
|
||||
|
||||
function Ring:Add(obj) -- Append at the "far end" of the ring (aka just before the current position)
|
||||
if self.pos then
|
||||
obj.prev = self.pos.prev
|
||||
obj.prev.next = obj
|
||||
obj.next = self.pos
|
||||
obj.next.prev = obj
|
||||
else
|
||||
obj.next = obj
|
||||
obj.prev = obj
|
||||
self.pos = obj
|
||||
end
|
||||
end
|
||||
|
||||
function Ring:Remove(obj)
|
||||
obj.next.prev = obj.prev
|
||||
obj.prev.next = obj.next
|
||||
if self.pos == obj then
|
||||
self.pos = obj.next
|
||||
if self.pos == obj then
|
||||
self.pos = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- Recycling bin for pipes
|
||||
-- A pipe is a plain integer-indexed queue, which also happens to be a ring member
|
||||
|
||||
ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
|
||||
local PipeBin = setmetatable({}, {__mode="k"})
|
||||
|
||||
local function DelPipe(pipe)
|
||||
for i = #pipe, 1, -1 do
|
||||
pipe[i] = nil
|
||||
end
|
||||
pipe.prev = nil
|
||||
pipe.next = nil
|
||||
|
||||
PipeBin[pipe] = true
|
||||
end
|
||||
|
||||
local function NewPipe()
|
||||
local pipe = next(PipeBin)
|
||||
if pipe then
|
||||
PipeBin[pipe] = nil
|
||||
return pipe
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- Recycling bin for messages
|
||||
|
||||
ChatThrottleLib.MsgBin = nil -- pre-v19, drastically different
|
||||
local MsgBin = setmetatable({}, {__mode="k"})
|
||||
|
||||
local function DelMsg(msg)
|
||||
msg[1] = nil
|
||||
-- there's more parameters, but they're very repetetive so the string pool doesn't suffer really, and it's faster to just not delete them.
|
||||
MsgBin[msg] = true
|
||||
end
|
||||
|
||||
local function NewMsg()
|
||||
local msg = next(MsgBin)
|
||||
if msg then
|
||||
MsgBin[msg] = nil
|
||||
return msg
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- ChatThrottleLib:Init
|
||||
-- Initialize queues, set up frame for OnUpdate, etc
|
||||
|
||||
|
||||
function ChatThrottleLib:Init()
|
||||
|
||||
-- Set up queues
|
||||
if not self.Prio then
|
||||
self.Prio = {}
|
||||
self.Prio["ALERT"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
|
||||
self.Prio["NORMAL"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
|
||||
self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
|
||||
end
|
||||
|
||||
-- v4: total send counters per priority
|
||||
for _, Prio in pairs(self.Prio) do
|
||||
Prio.nTotalSent = Prio.nTotalSent or 0
|
||||
end
|
||||
|
||||
if not self.avail then
|
||||
self.avail = 0 -- v5
|
||||
end
|
||||
if not self.nTotalSent then
|
||||
self.nTotalSent = 0 -- v5
|
||||
end
|
||||
|
||||
|
||||
-- Set up a frame to get OnUpdate events
|
||||
if not self.Frame then
|
||||
self.Frame = CreateFrame("Frame")
|
||||
self.Frame:Hide()
|
||||
end
|
||||
self.Frame:SetScript("OnUpdate", self.OnUpdate)
|
||||
self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds
|
||||
self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
self.OnUpdateDelay = 0
|
||||
self.LastAvailUpdate = GetTime()
|
||||
self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup
|
||||
|
||||
-- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7)
|
||||
if not self.securelyHooked then
|
||||
-- Use secure hooks as of v16. Old regular hook support yanked out in v21.
|
||||
self.securelyHooked = true
|
||||
--SendChatMessage
|
||||
hooksecurefunc("SendChatMessage", function(...)
|
||||
return ChatThrottleLib.Hook_SendChatMessage(...)
|
||||
end)
|
||||
--SendAddonMessage
|
||||
hooksecurefunc("SendAddonMessage", function(...)
|
||||
return ChatThrottleLib.Hook_SendAddonMessage(...)
|
||||
end)
|
||||
end
|
||||
self.nBypass = 0
|
||||
end
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage
|
||||
|
||||
local bMyTraffic = false
|
||||
|
||||
function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination, ...)
|
||||
if bMyTraffic then
|
||||
return
|
||||
end
|
||||
local self = ChatThrottleLib
|
||||
local size = strlen(tostring(text or "")) + strlen(tostring(destination or "")) + self.MSG_OVERHEAD
|
||||
self.avail = self.avail - size
|
||||
self.nBypass = self.nBypass + size -- just a statistic
|
||||
end
|
||||
function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...)
|
||||
if bMyTraffic then
|
||||
return
|
||||
end
|
||||
local self = ChatThrottleLib
|
||||
local size = tostring(text or ""):len() + tostring(prefix or ""):len();
|
||||
size = size + tostring(destination or ""):len() + self.MSG_OVERHEAD
|
||||
self.avail = self.avail - size
|
||||
self.nBypass = self.nBypass + size -- just a statistic
|
||||
end
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- ChatThrottleLib:UpdateAvail
|
||||
-- Update self.avail with how much bandwidth is currently available
|
||||
|
||||
function ChatThrottleLib:UpdateAvail()
|
||||
local now = GetTime()
|
||||
local MAX_CPS = self.MAX_CPS;
|
||||
local newavail = MAX_CPS * (now - self.LastAvailUpdate)
|
||||
local avail = self.avail
|
||||
|
||||
if now - self.HardThrottlingBeginTime < 5 then
|
||||
-- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then
|
||||
avail = math_min(avail + (newavail*0.1), MAX_CPS*0.5)
|
||||
self.bChoking = true
|
||||
elseif GetFramerate() < self.MIN_FPS then -- GetFrameRate call takes ~0.002 secs
|
||||
avail = math_min(MAX_CPS, avail + newavail*0.5)
|
||||
self.bChoking = true -- just a statistic
|
||||
else
|
||||
avail = math_min(self.BURST, avail + newavail)
|
||||
self.bChoking = false
|
||||
end
|
||||
|
||||
avail = math_max(avail, 0-(MAX_CPS*2)) -- Can go negative when someone is eating bandwidth past the lib. but we refuse to stay silent for more than 2 seconds; if they can do it, we can.
|
||||
|
||||
self.avail = avail
|
||||
self.LastAvailUpdate = now
|
||||
|
||||
return avail
|
||||
end
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- Despooling logic
|
||||
|
||||
function ChatThrottleLib:Despool(Prio)
|
||||
local ring = Prio.Ring
|
||||
while ring.pos and Prio.avail > ring.pos[1].nSize do
|
||||
local msg = table_remove(Prio.Ring.pos, 1)
|
||||
if not Prio.Ring.pos[1] then
|
||||
local pipe = Prio.Ring.pos
|
||||
Prio.Ring:Remove(pipe)
|
||||
Prio.ByName[pipe.name] = nil
|
||||
DelPipe(pipe)
|
||||
else
|
||||
Prio.Ring.pos = Prio.Ring.pos.next
|
||||
end
|
||||
Prio.avail = Prio.avail - msg.nSize
|
||||
bMyTraffic = true
|
||||
msg.f(unpack(msg, 1, msg.n))
|
||||
bMyTraffic = false
|
||||
Prio.nTotalSent = Prio.nTotalSent + msg.nSize
|
||||
DelMsg(msg)
|
||||
if msg.callbackFn then
|
||||
msg.callbackFn (msg.callbackArg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ChatThrottleLib.OnEvent(this,event)
|
||||
-- v11: We know that the rate limiter is touchy after login. Assume that it's touchy after zoning, too.
|
||||
local self = ChatThrottleLib
|
||||
if event == "PLAYER_ENTERING_WORLD" then
|
||||
self.HardThrottlingBeginTime = GetTime() -- Throttle hard for a few seconds after zoning
|
||||
self.avail = 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ChatThrottleLib.OnUpdate(this,delay)
|
||||
local self = ChatThrottleLib
|
||||
|
||||
self.OnUpdateDelay = self.OnUpdateDelay + delay
|
||||
if self.OnUpdateDelay < 0.08 then
|
||||
return
|
||||
end
|
||||
self.OnUpdateDelay = 0
|
||||
|
||||
self:UpdateAvail()
|
||||
|
||||
if self.avail < 0 then
|
||||
return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu.
|
||||
end
|
||||
|
||||
-- See how many of our priorities have queued messages (we only have 3, don't worry about the loop)
|
||||
local n = 0
|
||||
for prioname,Prio in pairs(self.Prio) do
|
||||
if Prio.Ring.pos or Prio.avail < 0 then
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Anything queued still?
|
||||
if n<1 then
|
||||
-- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing
|
||||
for prioname, Prio in pairs(self.Prio) do
|
||||
self.avail = self.avail + Prio.avail
|
||||
Prio.avail = 0
|
||||
end
|
||||
self.bQueueing = false
|
||||
self.Frame:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
-- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues
|
||||
local avail = self.avail/n
|
||||
self.avail = 0
|
||||
|
||||
for prioname, Prio in pairs(self.Prio) do
|
||||
if Prio.Ring.pos or Prio.avail < 0 then
|
||||
Prio.avail = Prio.avail + avail
|
||||
if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then
|
||||
self:Despool(Prio)
|
||||
-- Note: We might not get here if the user-supplied callback function errors out! Take care!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- Spooling logic
|
||||
|
||||
|
||||
function ChatThrottleLib:Enqueue(prioname, pipename, msg)
|
||||
local Prio = self.Prio[prioname]
|
||||
local pipe = Prio.ByName[pipename]
|
||||
if not pipe then
|
||||
self.Frame:Show()
|
||||
pipe = NewPipe()
|
||||
pipe.name = pipename
|
||||
Prio.ByName[pipename] = pipe
|
||||
Prio.Ring:Add(pipe)
|
||||
end
|
||||
|
||||
pipe[#pipe + 1] = msg
|
||||
|
||||
self.bQueueing = true
|
||||
end
|
||||
|
||||
|
||||
|
||||
function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg)
|
||||
if not self or not prio or not prefix or not text or not self.Prio[prio] then
|
||||
error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2)
|
||||
end
|
||||
if callbackFn and type(callbackFn)~="function" then
|
||||
error('ChatThrottleLib:ChatMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
|
||||
end
|
||||
|
||||
local nSize = text:len()
|
||||
|
||||
if nSize>255 then
|
||||
error("ChatThrottleLib:SendChatMessage(): message length cannot exceed 255 bytes", 2)
|
||||
end
|
||||
|
||||
nSize = nSize + self.MSG_OVERHEAD
|
||||
|
||||
-- Check if there's room in the global available bandwidth gauge to send directly
|
||||
if not self.bQueueing and nSize < self:UpdateAvail() then
|
||||
self.avail = self.avail - nSize
|
||||
bMyTraffic = true
|
||||
_G.SendChatMessage(text, chattype, language, destination)
|
||||
bMyTraffic = false
|
||||
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
|
||||
if callbackFn then
|
||||
callbackFn (callbackArg)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Message needs to be queued
|
||||
local msg = NewMsg()
|
||||
msg.f = _G.SendChatMessage
|
||||
msg[1] = text
|
||||
msg[2] = chattype or "SAY"
|
||||
msg[3] = language
|
||||
msg[4] = destination
|
||||
msg.n = 4
|
||||
msg.nSize = nSize
|
||||
msg.callbackFn = callbackFn
|
||||
msg.callbackArg = callbackArg
|
||||
|
||||
self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg)
|
||||
end
|
||||
|
||||
|
||||
function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
|
||||
if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
|
||||
error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
|
||||
end
|
||||
if callbackFn and type(callbackFn)~="function" then
|
||||
error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
|
||||
end
|
||||
|
||||
local nSize = prefix:len() + 1 + text:len();
|
||||
|
||||
if nSize>255 then
|
||||
error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2)
|
||||
end
|
||||
|
||||
nSize = nSize + self.MSG_OVERHEAD;
|
||||
|
||||
-- Check if there's room in the global available bandwidth gauge to send directly
|
||||
if not self.bQueueing and nSize < self:UpdateAvail() then
|
||||
self.avail = self.avail - nSize
|
||||
bMyTraffic = true
|
||||
_G.SendAddonMessage(prefix, text, chattype, target)
|
||||
bMyTraffic = false
|
||||
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
|
||||
if callbackFn then
|
||||
callbackFn (callbackArg)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Message needs to be queued
|
||||
local msg = NewMsg()
|
||||
msg.f = _G.SendAddonMessage
|
||||
msg[1] = prefix
|
||||
msg[2] = text
|
||||
msg[3] = chattype
|
||||
msg[4] = target
|
||||
msg.n = (target~=nil) and 4 or 3;
|
||||
msg.nSize = nSize
|
||||
msg.callbackFn = callbackFn
|
||||
msg.callbackArg = callbackArg
|
||||
|
||||
self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
-- Get the ball rolling!
|
||||
|
||||
ChatThrottleLib:Init()
|
||||
|
||||
--[[ WoWBench debugging snippet
|
||||
if(WOWB_VER) then
|
||||
local function SayTimer()
|
||||
print("SAY: "..GetTime().." "..arg1)
|
||||
end
|
||||
ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer)
|
||||
ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY")
|
||||
end
|
||||
]]
|
||||
|
||||
|
||||
@@ -0,0 +1,728 @@
|
||||
--- **AceDB-3.0** manages the SavedVariables of your addon.
|
||||
-- It offers profile management, smart defaults and namespaces for modules.\\
|
||||
-- Data can be saved in different data-types, depending on its intended usage.
|
||||
-- The most common data-type is the `profile` type, which allows the user to choose
|
||||
-- the active profile, and manage the profiles of all of his characters.\\
|
||||
-- The following data types are available:
|
||||
-- * **char** Character-specific data. Every character has its own database.
|
||||
-- * **realm** Realm-specific data. All of the players characters on the same realm share this database.
|
||||
-- * **class** Class-specific data. All of the players characters of the same class share this database.
|
||||
-- * **race** Race-specific data. All of the players characters of the same race share this database.
|
||||
-- * **faction** Faction-specific data. All of the players characters of the same faction share this database.
|
||||
-- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database.
|
||||
-- * **global** Global Data. All characters on the same account share this database.
|
||||
-- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used.
|
||||
--
|
||||
-- Creating a new Database using the `:New` function will return a new DBObject. A database will inherit all functions
|
||||
-- of the DBObjectLib listed here. \\
|
||||
-- If you create a new namespaced child-database (`:RegisterNamespace`), you'll get a DBObject as well, but note
|
||||
-- that the child-databases cannot individually change their profile, and are linked to their parents profile - and because of that,
|
||||
-- the profile related APIs are not available. Only `:RegisterDefaults` and `:ResetProfile` are available on child-databases.
|
||||
--
|
||||
-- For more details on how to use AceDB-3.0, see the [[AceDB-3.0 Tutorial]].
|
||||
--
|
||||
-- You may also be interested in [[libdualspec-1-0|LibDualSpec-1.0]] to do profile switching automatically when switching specs.
|
||||
--
|
||||
-- @usage
|
||||
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("DBExample")
|
||||
--
|
||||
-- -- declare defaults to be used in the DB
|
||||
-- local defaults = {
|
||||
-- profile = {
|
||||
-- setting = true,
|
||||
-- }
|
||||
-- }
|
||||
--
|
||||
-- function MyAddon:OnInitialize()
|
||||
-- -- Assuming the .toc says ## SavedVariables: MyAddonDB
|
||||
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true)
|
||||
-- end
|
||||
-- @class file
|
||||
-- @name AceDB-3.0.lua
|
||||
-- @release $Id: AceDB-3.0.lua 940 2010-06-19 08:01:47Z nevcairiel $
|
||||
local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 21
|
||||
local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR)
|
||||
|
||||
if not AceDB then return end -- No upgrade needed
|
||||
|
||||
-- Lua APIs
|
||||
local type, pairs, next, error = type, pairs, next, error
|
||||
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget
|
||||
|
||||
-- WoW APIs
|
||||
local _G = _G
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: LibStub
|
||||
|
||||
AceDB.db_registry = AceDB.db_registry or {}
|
||||
AceDB.frame = AceDB.frame or CreateFrame("Frame")
|
||||
|
||||
local CallbackHandler
|
||||
local CallbackDummy = { Fire = function() end }
|
||||
|
||||
local DBObjectLib = {}
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
AceDB Utility Functions
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
-- Simple shallow copy for copying defaults
|
||||
local function copyTable(src, dest)
|
||||
if type(dest) ~= "table" then dest = {} end
|
||||
if type(src) == "table" then
|
||||
for k,v in pairs(src) do
|
||||
if type(v) == "table" then
|
||||
-- try to index the key first so that the metatable creates the defaults, if set, and use that table
|
||||
v = copyTable(v, dest[k])
|
||||
end
|
||||
dest[k] = v
|
||||
end
|
||||
end
|
||||
return dest
|
||||
end
|
||||
|
||||
-- Called to add defaults to a section of the database
|
||||
--
|
||||
-- When a ["*"] default section is indexed with a new key, a table is returned
|
||||
-- and set in the host table. These tables must be cleaned up by removeDefaults
|
||||
-- in order to ensure we don't write empty default tables.
|
||||
local function copyDefaults(dest, src)
|
||||
-- this happens if some value in the SV overwrites our default value with a non-table
|
||||
--if type(dest) ~= "table" then return end
|
||||
for k, v in pairs(src) do
|
||||
if k == "*" or k == "**" then
|
||||
if type(v) == "table" then
|
||||
-- This is a metatable used for table defaults
|
||||
local mt = {
|
||||
-- This handles the lookup and creation of new subtables
|
||||
__index = function(t,k)
|
||||
if k == nil then return nil end
|
||||
local tbl = {}
|
||||
copyDefaults(tbl, v)
|
||||
rawset(t, k, tbl)
|
||||
return tbl
|
||||
end,
|
||||
}
|
||||
setmetatable(dest, mt)
|
||||
-- handle already existing tables in the SV
|
||||
for dk, dv in pairs(dest) do
|
||||
if not rawget(src, dk) and type(dv) == "table" then
|
||||
copyDefaults(dv, v)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Values are not tables, so this is just a simple return
|
||||
local mt = {__index = function(t,k) return k~=nil and v or nil end}
|
||||
setmetatable(dest, mt)
|
||||
end
|
||||
elseif type(v) == "table" then
|
||||
if not rawget(dest, k) then rawset(dest, k, {}) end
|
||||
if type(dest[k]) == "table" then
|
||||
copyDefaults(dest[k], v)
|
||||
if src['**'] then
|
||||
copyDefaults(dest[k], src['**'])
|
||||
end
|
||||
end
|
||||
else
|
||||
if rawget(dest, k) == nil then
|
||||
rawset(dest, k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Called to remove all defaults in the default table from the database
|
||||
local function removeDefaults(db, defaults, blocker)
|
||||
-- remove all metatables from the db, so we don't accidentally create new sub-tables through them
|
||||
setmetatable(db, nil)
|
||||
-- loop through the defaults and remove their content
|
||||
for k,v in pairs(defaults) do
|
||||
if k == "*" or k == "**" then
|
||||
if type(v) == "table" then
|
||||
-- Loop through all the actual k,v pairs and remove
|
||||
for key, value in pairs(db) do
|
||||
if type(value) == "table" then
|
||||
-- if the key was not explicitly specified in the defaults table, just strip everything from * and ** tables
|
||||
if defaults[key] == nil and (not blocker or blocker[key] == nil) then
|
||||
removeDefaults(value, v)
|
||||
-- if the table is empty afterwards, remove it
|
||||
if next(value) == nil then
|
||||
db[key] = nil
|
||||
end
|
||||
-- if it was specified, only strip ** content, but block values which were set in the key table
|
||||
elseif k == "**" then
|
||||
removeDefaults(value, v, defaults[key])
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif k == "*" then
|
||||
-- check for non-table default
|
||||
for key, value in pairs(db) do
|
||||
if defaults[key] == nil and v == value then
|
||||
db[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif type(v) == "table" and type(db[k]) == "table" then
|
||||
-- if a blocker was set, dive into it, to allow multi-level defaults
|
||||
removeDefaults(db[k], v, blocker and blocker[k])
|
||||
if next(db[k]) == nil then
|
||||
db[k] = nil
|
||||
end
|
||||
else
|
||||
-- check if the current value matches the default, and that its not blocked by another defaults table
|
||||
if db[k] == defaults[k] and (not blocker or blocker[k] == nil) then
|
||||
db[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- This is called when a table section is first accessed, to set up the defaults
|
||||
local function initSection(db, section, svstore, key, defaults)
|
||||
local sv = rawget(db, "sv")
|
||||
|
||||
local tableCreated
|
||||
if not sv[svstore] then sv[svstore] = {} end
|
||||
if not sv[svstore][key] then
|
||||
sv[svstore][key] = {}
|
||||
tableCreated = true
|
||||
end
|
||||
|
||||
local tbl = sv[svstore][key]
|
||||
|
||||
if defaults then
|
||||
copyDefaults(tbl, defaults)
|
||||
end
|
||||
rawset(db, section, tbl)
|
||||
|
||||
return tableCreated, tbl
|
||||
end
|
||||
|
||||
-- Metatable to handle the dynamic creation of sections and copying of sections.
|
||||
local dbmt = {
|
||||
__index = function(t, section)
|
||||
local keys = rawget(t, "keys")
|
||||
local key = keys[section]
|
||||
if key then
|
||||
local defaultTbl = rawget(t, "defaults")
|
||||
local defaults = defaultTbl and defaultTbl[section]
|
||||
|
||||
if section == "profile" then
|
||||
local new = initSection(t, section, "profiles", key, defaults)
|
||||
if new then
|
||||
-- Callback: OnNewProfile, database, newProfileKey
|
||||
t.callbacks:Fire("OnNewProfile", t, key)
|
||||
end
|
||||
elseif section == "profiles" then
|
||||
local sv = rawget(t, "sv")
|
||||
if not sv.profiles then sv.profiles = {} end
|
||||
rawset(t, "profiles", sv.profiles)
|
||||
elseif section == "global" then
|
||||
local sv = rawget(t, "sv")
|
||||
if not sv.global then sv.global = {} end
|
||||
if defaults then
|
||||
copyDefaults(sv.global, defaults)
|
||||
end
|
||||
rawset(t, section, sv.global)
|
||||
else
|
||||
initSection(t, section, section, key, defaults)
|
||||
end
|
||||
end
|
||||
|
||||
return rawget(t, section)
|
||||
end
|
||||
}
|
||||
|
||||
local function validateDefaults(defaults, keyTbl, offset)
|
||||
if not defaults then return end
|
||||
offset = offset or 0
|
||||
for k in pairs(defaults) do
|
||||
if not keyTbl[k] or k == "profiles" then
|
||||
error(("Usage: AceDBObject:RegisterDefaults(defaults): '%s' is not a valid datatype."):format(k), 3 + offset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local preserve_keys = {
|
||||
["callbacks"] = true,
|
||||
["RegisterCallback"] = true,
|
||||
["UnregisterCallback"] = true,
|
||||
["UnregisterAllCallbacks"] = true,
|
||||
["children"] = true,
|
||||
}
|
||||
|
||||
local realmKey = GetRealmName()
|
||||
local charKey = UnitName("player") .. " - " .. realmKey
|
||||
local _, classKey = UnitClass("player")
|
||||
local _, raceKey = UnitRace("player")
|
||||
local factionKey = UnitFactionGroup("player")
|
||||
local factionrealmKey = factionKey .. " - " .. realmKey
|
||||
-- Actual database initialization function
|
||||
local function initdb(sv, defaults, defaultProfile, olddb, parent)
|
||||
-- Generate the database keys for each section
|
||||
|
||||
-- map "true" to our "Default" profile
|
||||
if defaultProfile == true then defaultProfile = "Default" end
|
||||
|
||||
local profileKey
|
||||
if not parent then
|
||||
-- Make a container for profile keys
|
||||
if not sv.profileKeys then sv.profileKeys = {} end
|
||||
|
||||
-- Try to get the profile selected from the char db
|
||||
profileKey = sv.profileKeys[charKey] or defaultProfile or charKey
|
||||
|
||||
-- save the selected profile for later
|
||||
sv.profileKeys[charKey] = profileKey
|
||||
else
|
||||
-- Use the profile of the parents DB
|
||||
profileKey = parent.keys.profile or defaultProfile or charKey
|
||||
|
||||
-- clear the profileKeys in the DB, namespaces don't need to store them
|
||||
sv.profileKeys = nil
|
||||
end
|
||||
|
||||
-- This table contains keys that enable the dynamic creation
|
||||
-- of each section of the table. The 'global' and 'profiles'
|
||||
-- have a key of true, since they are handled in a special case
|
||||
local keyTbl= {
|
||||
["char"] = charKey,
|
||||
["realm"] = realmKey,
|
||||
["class"] = classKey,
|
||||
["race"] = raceKey,
|
||||
["faction"] = factionKey,
|
||||
["factionrealm"] = factionrealmKey,
|
||||
["profile"] = profileKey,
|
||||
["global"] = true,
|
||||
["profiles"] = true,
|
||||
}
|
||||
|
||||
validateDefaults(defaults, keyTbl, 1)
|
||||
|
||||
-- This allows us to use this function to reset an entire database
|
||||
-- Clear out the old database
|
||||
if olddb then
|
||||
for k,v in pairs(olddb) do if not preserve_keys[k] then olddb[k] = nil end end
|
||||
end
|
||||
|
||||
-- Give this database the metatable so it initializes dynamically
|
||||
local db = setmetatable(olddb or {}, dbmt)
|
||||
|
||||
if not rawget(db, "callbacks") then
|
||||
-- try to load CallbackHandler-1.0 if it loaded after our library
|
||||
if not CallbackHandler then CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0", true) end
|
||||
db.callbacks = CallbackHandler and CallbackHandler:New(db) or CallbackDummy
|
||||
end
|
||||
|
||||
-- Copy methods locally into the database object, to avoid hitting
|
||||
-- the metatable when calling methods
|
||||
|
||||
if not parent then
|
||||
for name, func in pairs(DBObjectLib) do
|
||||
db[name] = func
|
||||
end
|
||||
else
|
||||
-- hack this one in
|
||||
db.RegisterDefaults = DBObjectLib.RegisterDefaults
|
||||
db.ResetProfile = DBObjectLib.ResetProfile
|
||||
end
|
||||
|
||||
-- Set some properties in the database object
|
||||
db.profiles = sv.profiles
|
||||
db.keys = keyTbl
|
||||
db.sv = sv
|
||||
--db.sv_name = name
|
||||
db.defaults = defaults
|
||||
db.parent = parent
|
||||
|
||||
-- store the DB in the registry
|
||||
AceDB.db_registry[db] = true
|
||||
|
||||
return db
|
||||
end
|
||||
|
||||
-- handle PLAYER_LOGOUT
|
||||
-- strip all defaults from all databases
|
||||
-- and cleans up empty sections
|
||||
local function logoutHandler(frame, event)
|
||||
if event == "PLAYER_LOGOUT" then
|
||||
for db in pairs(AceDB.db_registry) do
|
||||
db.callbacks:Fire("OnDatabaseShutdown", db)
|
||||
db:RegisterDefaults(nil)
|
||||
|
||||
-- cleanup sections that are empty without defaults
|
||||
local sv = rawget(db, "sv")
|
||||
for section in pairs(db.keys) do
|
||||
if rawget(sv, section) then
|
||||
-- global is special, all other sections have sub-entrys
|
||||
-- also don't delete empty profiles on main dbs, only on namespaces
|
||||
if section ~= "global" and (section ~= "profiles" or rawget(db, "parent")) then
|
||||
for key in pairs(sv[section]) do
|
||||
if not next(sv[section][key]) then
|
||||
sv[section][key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
if not next(sv[section]) then
|
||||
sv[section] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
AceDB.frame:RegisterEvent("PLAYER_LOGOUT")
|
||||
AceDB.frame:SetScript("OnEvent", logoutHandler)
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
AceDB Object Method Definitions
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
--- Sets the defaults table for the given database object by clearing any
|
||||
-- that are currently set, and then setting the new defaults.
|
||||
-- @param defaults A table of defaults for this database
|
||||
function DBObjectLib:RegisterDefaults(defaults)
|
||||
if defaults and type(defaults) ~= "table" then
|
||||
error("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected.", 2)
|
||||
end
|
||||
|
||||
validateDefaults(defaults, self.keys)
|
||||
|
||||
-- Remove any currently set defaults
|
||||
if self.defaults then
|
||||
for section,key in pairs(self.keys) do
|
||||
if self.defaults[section] and rawget(self, section) then
|
||||
removeDefaults(self[section], self.defaults[section])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Set the DBObject.defaults table
|
||||
self.defaults = defaults
|
||||
|
||||
-- Copy in any defaults, only touching those sections already created
|
||||
if defaults then
|
||||
for section,key in pairs(self.keys) do
|
||||
if defaults[section] and rawget(self, section) then
|
||||
copyDefaults(self[section], defaults[section])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Changes the profile of the database and all of it's namespaces to the
|
||||
-- supplied named profile
|
||||
-- @param name The name of the profile to set as the current profile
|
||||
function DBObjectLib:SetProfile(name)
|
||||
if type(name) ~= "string" then
|
||||
error("Usage: AceDBObject:SetProfile(name): 'name' - string expected.", 2)
|
||||
end
|
||||
|
||||
-- changing to the same profile, dont do anything
|
||||
if name == self.keys.profile then return end
|
||||
|
||||
local oldProfile = self.profile
|
||||
local defaults = self.defaults and self.defaults.profile
|
||||
|
||||
-- Callback: OnProfileShutdown, database
|
||||
self.callbacks:Fire("OnProfileShutdown", self)
|
||||
|
||||
if oldProfile and defaults then
|
||||
-- Remove the defaults from the old profile
|
||||
removeDefaults(oldProfile, defaults)
|
||||
end
|
||||
|
||||
self.profile = nil
|
||||
self.keys["profile"] = name
|
||||
|
||||
-- if the storage exists, save the new profile
|
||||
-- this won't exist on namespaces.
|
||||
if self.sv.profileKeys then
|
||||
self.sv.profileKeys[charKey] = name
|
||||
end
|
||||
|
||||
-- populate to child namespaces
|
||||
if self.children then
|
||||
for _, db in pairs(self.children) do
|
||||
DBObjectLib.SetProfile(db, name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Callback: OnProfileChanged, database, newProfileKey
|
||||
self.callbacks:Fire("OnProfileChanged", self, name)
|
||||
end
|
||||
|
||||
--- Returns a table with the names of the existing profiles in the database.
|
||||
-- You can optionally supply a table to re-use for this purpose.
|
||||
-- @param tbl A table to store the profile names in (optional)
|
||||
function DBObjectLib:GetProfiles(tbl)
|
||||
if tbl and type(tbl) ~= "table" then
|
||||
error("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected.", 2)
|
||||
end
|
||||
|
||||
-- Clear the container table
|
||||
if tbl then
|
||||
for k,v in pairs(tbl) do tbl[k] = nil end
|
||||
else
|
||||
tbl = {}
|
||||
end
|
||||
|
||||
local curProfile = self.keys.profile
|
||||
|
||||
local i = 0
|
||||
for profileKey in pairs(self.profiles) do
|
||||
i = i + 1
|
||||
tbl[i] = profileKey
|
||||
if curProfile and profileKey == curProfile then curProfile = nil end
|
||||
end
|
||||
|
||||
-- Add the current profile, if it hasn't been created yet
|
||||
if curProfile then
|
||||
i = i + 1
|
||||
tbl[i] = curProfile
|
||||
end
|
||||
|
||||
return tbl, i
|
||||
end
|
||||
|
||||
--- Returns the current profile name used by the database
|
||||
function DBObjectLib:GetCurrentProfile()
|
||||
return self.keys.profile
|
||||
end
|
||||
|
||||
--- Deletes a named profile. This profile must not be the active profile.
|
||||
-- @param name The name of the profile to be deleted
|
||||
-- @param silent If true, do not raise an error when the profile does not exist
|
||||
function DBObjectLib:DeleteProfile(name, silent)
|
||||
if type(name) ~= "string" then
|
||||
error("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected.", 2)
|
||||
end
|
||||
|
||||
if self.keys.profile == name then
|
||||
error("Cannot delete the active profile in an AceDBObject.", 2)
|
||||
end
|
||||
|
||||
if not rawget(self.profiles, name) and not silent then
|
||||
error("Cannot delete profile '" .. name .. "'. It does not exist.", 2)
|
||||
end
|
||||
|
||||
self.profiles[name] = nil
|
||||
|
||||
-- populate to child namespaces
|
||||
if self.children then
|
||||
for _, db in pairs(self.children) do
|
||||
DBObjectLib.DeleteProfile(db, name, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- Callback: OnProfileDeleted, database, profileKey
|
||||
self.callbacks:Fire("OnProfileDeleted", self, name)
|
||||
end
|
||||
|
||||
--- Copies a named profile into the current profile, overwriting any conflicting
|
||||
-- settings.
|
||||
-- @param name The name of the profile to be copied into the current profile
|
||||
-- @param silent If true, do not raise an error when the profile does not exist
|
||||
function DBObjectLib:CopyProfile(name, silent)
|
||||
if type(name) ~= "string" then
|
||||
error("Usage: AceDBObject:CopyProfile(name): 'name' - string expected.", 2)
|
||||
end
|
||||
|
||||
if name == self.keys.profile then
|
||||
error("Cannot have the same source and destination profiles.", 2)
|
||||
end
|
||||
|
||||
if not rawget(self.profiles, name) and not silent then
|
||||
error("Cannot copy profile '" .. name .. "'. It does not exist.", 2)
|
||||
end
|
||||
|
||||
-- Reset the profile before copying
|
||||
DBObjectLib.ResetProfile(self, nil, true)
|
||||
|
||||
local profile = self.profile
|
||||
local source = self.profiles[name]
|
||||
|
||||
copyTable(source, profile)
|
||||
|
||||
-- populate to child namespaces
|
||||
if self.children then
|
||||
for _, db in pairs(self.children) do
|
||||
DBObjectLib.CopyProfile(db, name, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- Callback: OnProfileCopied, database, sourceProfileKey
|
||||
self.callbacks:Fire("OnProfileCopied", self, name)
|
||||
end
|
||||
|
||||
--- Resets the current profile to the default values (if specified).
|
||||
-- @param noChildren if set to true, the reset will not be populated to the child namespaces of this DB object
|
||||
-- @param noCallbacks if set to true, won't fire the OnProfileReset callback
|
||||
function DBObjectLib:ResetProfile(noChildren, noCallbacks)
|
||||
local profile = self.profile
|
||||
|
||||
for k,v in pairs(profile) do
|
||||
profile[k] = nil
|
||||
end
|
||||
|
||||
local defaults = self.defaults and self.defaults.profile
|
||||
if defaults then
|
||||
copyDefaults(profile, defaults)
|
||||
end
|
||||
|
||||
-- populate to child namespaces
|
||||
if self.children and not noChildren then
|
||||
for _, db in pairs(self.children) do
|
||||
DBObjectLib.ResetProfile(db, nil, noCallbacks)
|
||||
end
|
||||
end
|
||||
|
||||
-- Callback: OnProfileReset, database
|
||||
if not noCallbacks then
|
||||
self.callbacks:Fire("OnProfileReset", self)
|
||||
end
|
||||
end
|
||||
|
||||
--- Resets the entire database, using the string defaultProfile as the new default
|
||||
-- profile.
|
||||
-- @param defaultProfile The profile name to use as the default
|
||||
function DBObjectLib:ResetDB(defaultProfile)
|
||||
if defaultProfile and type(defaultProfile) ~= "string" then
|
||||
error("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected.", 2)
|
||||
end
|
||||
|
||||
local sv = self.sv
|
||||
for k,v in pairs(sv) do
|
||||
sv[k] = nil
|
||||
end
|
||||
|
||||
local parent = self.parent
|
||||
|
||||
initdb(sv, self.defaults, defaultProfile, self)
|
||||
|
||||
-- fix the child namespaces
|
||||
if self.children then
|
||||
if not sv.namespaces then sv.namespaces = {} end
|
||||
for name, db in pairs(self.children) do
|
||||
if not sv.namespaces[name] then sv.namespaces[name] = {} end
|
||||
initdb(sv.namespaces[name], db.defaults, self.keys.profile, db, self)
|
||||
end
|
||||
end
|
||||
|
||||
-- Callback: OnDatabaseReset, database
|
||||
self.callbacks:Fire("OnDatabaseReset", self)
|
||||
-- Callback: OnProfileChanged, database, profileKey
|
||||
self.callbacks:Fire("OnProfileChanged", self, self.keys["profile"])
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Creates a new database namespace, directly tied to the database. This
|
||||
-- is a full scale database in it's own rights other than the fact that
|
||||
-- it cannot control its profile individually
|
||||
-- @param name The name of the new namespace
|
||||
-- @param defaults A table of values to use as defaults
|
||||
function DBObjectLib:RegisterNamespace(name, defaults)
|
||||
if type(name) ~= "string" then
|
||||
error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected.", 2)
|
||||
end
|
||||
if defaults and type(defaults) ~= "table" then
|
||||
error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected.", 2)
|
||||
end
|
||||
if self.children and self.children[name] then
|
||||
error ("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace with that name already exists.", 2)
|
||||
end
|
||||
|
||||
local sv = self.sv
|
||||
if not sv.namespaces then sv.namespaces = {} end
|
||||
if not sv.namespaces[name] then
|
||||
sv.namespaces[name] = {}
|
||||
end
|
||||
|
||||
local newDB = initdb(sv.namespaces[name], defaults, self.keys.profile, nil, self)
|
||||
|
||||
if not self.children then self.children = {} end
|
||||
self.children[name] = newDB
|
||||
return newDB
|
||||
end
|
||||
|
||||
--- Returns an already existing namespace from the database object.
|
||||
-- @param name The name of the new namespace
|
||||
-- @param silent if true, the addon is optional, silently return nil if its not found
|
||||
-- @usage
|
||||
-- local namespace = self.db:GetNamespace('namespace')
|
||||
-- @return the namespace object if found
|
||||
function DBObjectLib:GetNamespace(name, silent)
|
||||
if type(name) ~= "string" then
|
||||
error("Usage: AceDBObject:GetNamespace(name): 'name' - string expected.", 2)
|
||||
end
|
||||
if not silent and not (self.children and self.children[name]) then
|
||||
error ("Usage: AceDBObject:GetNamespace(name): 'name' - namespace does not exist.", 2)
|
||||
end
|
||||
if not self.children then self.children = {} end
|
||||
return self.children[name]
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
AceDB Exposed Methods
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
--- Creates a new database object that can be used to handle database settings and profiles.
|
||||
-- By default, an empty DB is created, using a character specific profile.
|
||||
--
|
||||
-- You can override the default profile used by passing any profile name as the third argument,
|
||||
-- or by passing //true// as the third argument to use a globally shared profile called "Default".
|
||||
--
|
||||
-- Note that there is no token replacement in the default profile name, passing a defaultProfile as "char"
|
||||
-- will use a profile named "char", and not a character-specific profile.
|
||||
-- @param tbl The name of variable, or table to use for the database
|
||||
-- @param defaults A table of database defaults
|
||||
-- @param defaultProfile The name of the default profile. If not set, a character specific profile will be used as the default.
|
||||
-- You can also pass //true// to use a shared global profile called "Default".
|
||||
-- @usage
|
||||
-- -- Create an empty DB using a character-specific default profile.
|
||||
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB")
|
||||
-- @usage
|
||||
-- -- Create a DB using defaults and using a shared default profile
|
||||
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true)
|
||||
function AceDB:New(tbl, defaults, defaultProfile)
|
||||
if type(tbl) == "string" then
|
||||
local name = tbl
|
||||
tbl = _G[name]
|
||||
if not tbl then
|
||||
tbl = {}
|
||||
_G[name] = tbl
|
||||
end
|
||||
end
|
||||
|
||||
if type(tbl) ~= "table" then
|
||||
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected.", 2)
|
||||
end
|
||||
|
||||
if defaults and type(defaults) ~= "table" then
|
||||
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected.", 2)
|
||||
end
|
||||
|
||||
if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then
|
||||
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected.", 2)
|
||||
end
|
||||
|
||||
return initdb(tbl, defaults, defaultProfile)
|
||||
end
|
||||
|
||||
-- upgrade existing databases
|
||||
for db in pairs(AceDB.db_registry) do
|
||||
if not db.parent then
|
||||
for name,func in pairs(DBObjectLib) do
|
||||
db[name] = func
|
||||
end
|
||||
else
|
||||
db.RegisterDefaults = DBObjectLib.RegisterDefaults
|
||||
db.ResetProfile = DBObjectLib.ResetProfile
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceDB-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -0,0 +1,799 @@
|
||||
--[[
|
||||
Name: AceLibrary
|
||||
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: 1091 $
|
||||
Website: http://www.wowace.com/
|
||||
Documentation: http://www.wowace.com/index.php/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
|
||||
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 = 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
|
||||
|
||||
-- @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 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 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
|
||||
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_argCheck, old_pcall
|
||||
if isAceLibrary then
|
||||
self = instance
|
||||
AceLibrary = instance
|
||||
|
||||
old_error = instance.error
|
||||
old_argCheck = instance.argCheck
|
||||
old_pcall = instance.pcall
|
||||
|
||||
self.error = error
|
||||
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 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 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)
|
||||
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)
|
||||
@@ -0,0 +1,15 @@
|
||||
## Interface: 30300
|
||||
## X-Curse-Packaged-Version: r1101
|
||||
## 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
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
--- **AceLocale-3.0** manages localization in addons, allowing for multiple locale to be registered with fallback to the base locale for untranslated strings.
|
||||
-- @class file
|
||||
-- @name AceLocale-3.0
|
||||
-- @release $Id: AceLocale-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
|
||||
local MAJOR,MINOR = "AceLocale-3.0", 2
|
||||
|
||||
local AceLocale, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceLocale then return end -- no upgrade needed
|
||||
|
||||
-- Lua APIs
|
||||
local assert, tostring, error = assert, tostring, error
|
||||
local setmetatable, rawset, rawget = setmetatable, rawset, rawget
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: GAME_LOCALE, geterrorhandler
|
||||
|
||||
local gameLocale = GetLocale()
|
||||
if gameLocale == "enGB" then
|
||||
gameLocale = "enUS"
|
||||
end
|
||||
|
||||
AceLocale.apps = AceLocale.apps or {} -- array of ["AppName"]=localetableref
|
||||
AceLocale.appnames = AceLocale.appnames or {} -- array of [localetableref]="AppName"
|
||||
|
||||
-- This metatable is used on all tables returned from GetLocale
|
||||
local readmeta = {
|
||||
__index = function(self, key) -- requesting totally unknown entries: fire off a nonbreaking error and return key
|
||||
rawset(self, key, key) -- only need to see the warning once, really
|
||||
geterrorhandler()(MAJOR..": "..tostring(AceLocale.appnames[self])..": Missing entry for '"..tostring(key).."'")
|
||||
return key
|
||||
end
|
||||
}
|
||||
|
||||
-- This metatable is used on all tables returned from GetLocale if the silent flag is true, it does not issue a warning on unknown keys
|
||||
local readmetasilent = {
|
||||
__index = function(self, key) -- requesting totally unknown entries: return key
|
||||
rawset(self, key, key) -- only need to invoke this function once
|
||||
return key
|
||||
end
|
||||
}
|
||||
|
||||
-- Remember the locale table being registered right now (it gets set by :NewLocale())
|
||||
-- NOTE: Do never try to register 2 locale tables at once and mix their definition.
|
||||
local registering
|
||||
|
||||
-- local assert false function
|
||||
local assertfalse = function() assert(false) end
|
||||
|
||||
-- This metatable proxy is used when registering nondefault locales
|
||||
local writeproxy = setmetatable({}, {
|
||||
__newindex = function(self, key, value)
|
||||
rawset(registering, key, value == true and key or value) -- assigning values: replace 'true' with key string
|
||||
end,
|
||||
__index = assertfalse
|
||||
})
|
||||
|
||||
-- This metatable proxy is used when registering the default locale.
|
||||
-- It refuses to overwrite existing values
|
||||
-- Reason 1: Allows loading locales in any order
|
||||
-- Reason 2: If 2 modules have the same string, but only the first one to be
|
||||
-- loaded has a translation for the current locale, the translation
|
||||
-- doesn't get overwritten.
|
||||
--
|
||||
local writedefaultproxy = setmetatable({}, {
|
||||
__newindex = function(self, key, value)
|
||||
if not rawget(registering, key) then
|
||||
rawset(registering, key, value == true and key or value)
|
||||
end
|
||||
end,
|
||||
__index = assertfalse
|
||||
})
|
||||
|
||||
--- Register a new locale (or extend an existing one) for the specified application.
|
||||
-- :NewLocale will return a table you can fill your locale into, or nil if the locale isn't needed for the players
|
||||
-- game locale.
|
||||
-- @paramsig application, locale[, isDefault[, silent]]
|
||||
-- @param application Unique name of addon / module
|
||||
-- @param locale Name of the locale to register, e.g. "enUS", "deDE", etc.
|
||||
-- @param isDefault If this is the default locale being registered (your addon is written in this language, generally enUS)
|
||||
-- @param silent If true, the locale will not issue warnings for missing keys. Can only be set on the default locale.
|
||||
-- @usage
|
||||
-- -- enUS.lua
|
||||
-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "enUS", true)
|
||||
-- L["string1"] = true
|
||||
--
|
||||
-- -- deDE.lua
|
||||
-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "deDE")
|
||||
-- if not L then return end
|
||||
-- L["string1"] = "Zeichenkette1"
|
||||
-- @return Locale Table to add localizations to, or nil if the current locale is not required.
|
||||
function AceLocale:NewLocale(application, locale, isDefault, silent)
|
||||
|
||||
if silent and not isDefault then
|
||||
error("Usage: NewLocale(application, locale[, isDefault[, silent]]): 'silent' can only be specified for the default locale", 2)
|
||||
end
|
||||
|
||||
-- GAME_LOCALE allows translators to test translations of addons without having that wow client installed
|
||||
-- Ammo: I still think this is a bad idea, for instance an addon that checks for some ingame string will fail, just because some other addon
|
||||
-- gives the user the illusion that they can run in a different locale? Ditch this whole thing or allow a setting per 'application'. I'm of the
|
||||
-- opinion to remove this.
|
||||
local gameLocale = GAME_LOCALE or gameLocale
|
||||
|
||||
if locale ~= gameLocale and not isDefault then
|
||||
return -- nop, we don't need these translations
|
||||
end
|
||||
|
||||
local app = AceLocale.apps[application]
|
||||
|
||||
if not app then
|
||||
app = setmetatable({}, silent and readmetasilent or readmeta)
|
||||
AceLocale.apps[application] = app
|
||||
AceLocale.appnames[app] = application
|
||||
end
|
||||
|
||||
registering = app -- remember globally for writeproxy and writedefaultproxy
|
||||
|
||||
if isDefault then
|
||||
return writedefaultproxy
|
||||
end
|
||||
|
||||
return writeproxy
|
||||
end
|
||||
|
||||
--- Returns localizations for the current locale (or default locale if translations are missing).
|
||||
-- Errors if nothing is registered (spank developer, not just a missing translation)
|
||||
-- @param application Unique name of addon / module
|
||||
-- @param silent If true, the locale is optional, silently return nil if it's not found (defaults to false, optional)
|
||||
-- @return The locale table for the current language.
|
||||
function AceLocale:GetLocale(application, silent)
|
||||
if not silent and not AceLocale.apps[application] then
|
||||
error("Usage: GetLocale(application[, silent]): 'application' - No locales registered for '"..tostring(application).."'", 2)
|
||||
end
|
||||
return AceLocale.apps[application]
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceLocale-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -0,0 +1,281 @@
|
||||
--- **AceSerializer-3.0** can serialize any variable (except functions or userdata) into a string format,
|
||||
-- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially
|
||||
-- very large numbers or floating point numbers, and table structures. The only caveat currently is, that multiple
|
||||
-- references to the same table will be send individually.
|
||||
--
|
||||
-- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer:Embed(MyAddon) or by
|
||||
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
|
||||
-- and can be accessed directly, without having to explicitly call AceSerializer itself.\\
|
||||
-- It is recommended to embed AceSerializer, otherwise you'll have to specify a custom `self` on all calls you
|
||||
-- make into AceSerializer.
|
||||
-- @class file
|
||||
-- @name AceSerializer-3.0
|
||||
-- @release $Id: AceSerializer-3.0.lua 910 2010-02-11 21:54:24Z mikk $
|
||||
local MAJOR,MINOR = "AceSerializer-3.0", 3
|
||||
local AceSerializer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceSerializer then return end
|
||||
|
||||
-- Lua APIs
|
||||
local strbyte, strchar, gsub, gmatch, format = string.byte, string.char, string.gsub, string.gmatch, string.format
|
||||
local assert, error, pcall = assert, error, pcall
|
||||
local type, tostring, tonumber = type, tostring, tonumber
|
||||
local pairs, select, frexp = pairs, select, math.frexp
|
||||
local tconcat = table.concat
|
||||
|
||||
-- quick copies of string representations of wonky numbers
|
||||
local serNaN = tostring(0/0)
|
||||
local serInf = tostring(1/0)
|
||||
local serNegInf = tostring(-1/0)
|
||||
|
||||
|
||||
-- Serialization functions
|
||||
|
||||
local function SerializeStringHelper(ch) -- Used by SerializeValue for strings
|
||||
-- We use \126 ("~") as an escape character for all nonprints plus a few more
|
||||
local n = strbyte(ch)
|
||||
if n==30 then -- v3 / ticket 115: catch a nonprint that ends up being "~^" when encoded... DOH
|
||||
return "\126\122"
|
||||
elseif n<=32 then -- nonprint + space
|
||||
return "\126"..strchar(n+64)
|
||||
elseif n==94 then -- value separator
|
||||
return "\126\125"
|
||||
elseif n==126 then -- our own escape character
|
||||
return "\126\124"
|
||||
elseif n==127 then -- nonprint (DEL)
|
||||
return "\126\123"
|
||||
else
|
||||
assert(false) -- can't be reached if caller uses a sane regex
|
||||
end
|
||||
end
|
||||
|
||||
local function SerializeValue(v, res, nres)
|
||||
-- We use "^" as a value separator, followed by one byte for type indicator
|
||||
local t=type(v)
|
||||
|
||||
if t=="string" then -- ^S = string (escaped to remove nonprints, "^"s, etc)
|
||||
res[nres+1] = "^S"
|
||||
res[nres+2] = gsub(v,"[%c \94\126\127]", SerializeStringHelper)
|
||||
nres=nres+2
|
||||
|
||||
elseif t=="number" then -- ^N = number (just tostring()ed) or ^F (float components)
|
||||
local str = tostring(v)
|
||||
if tonumber(str)==v or str==serNaN or str==serInf or str==serNegInf then
|
||||
-- translates just fine, transmit as-is
|
||||
res[nres+1] = "^N"
|
||||
res[nres+2] = str
|
||||
nres=nres+2
|
||||
else
|
||||
local m,e = frexp(v)
|
||||
res[nres+1] = "^F"
|
||||
res[nres+2] = format("%.0f",m*2^53) -- force mantissa to become integer (it's originally 0.5--0.9999)
|
||||
res[nres+3] = "^f"
|
||||
res[nres+4] = tostring(e-53) -- adjust exponent to counteract mantissa manipulation
|
||||
nres=nres+4
|
||||
end
|
||||
|
||||
elseif t=="table" then -- ^T...^t = table (list of key,value pairs)
|
||||
nres=nres+1
|
||||
res[nres] = "^T"
|
||||
for k,v in pairs(v) do
|
||||
nres = SerializeValue(k, res, nres)
|
||||
nres = SerializeValue(v, res, nres)
|
||||
end
|
||||
nres=nres+1
|
||||
res[nres] = "^t"
|
||||
|
||||
elseif t=="boolean" then -- ^B = true, ^b = false
|
||||
nres=nres+1
|
||||
if v then
|
||||
res[nres] = "^B" -- true
|
||||
else
|
||||
res[nres] = "^b" -- false
|
||||
end
|
||||
|
||||
elseif t=="nil" then -- ^Z = nil (zero, "N" was taken :P)
|
||||
nres=nres+1
|
||||
res[nres] = "^Z"
|
||||
|
||||
else
|
||||
error(MAJOR..": Cannot serialize a value of type '"..t.."'") -- can't produce error on right level, this is wildly recursive
|
||||
end
|
||||
|
||||
return nres
|
||||
end
|
||||
|
||||
|
||||
|
||||
local serializeTbl = { "^1" } -- "^1" = Hi, I'm data serialized by AceSerializer protocol rev 1
|
||||
|
||||
--- Serialize the data passed into the function.
|
||||
-- Takes a list of values (strings, numbers, booleans, nils, tables)
|
||||
-- and returns it in serialized form (a string).\\
|
||||
-- May throw errors on invalid data types.
|
||||
-- @param ... List of values to serialize
|
||||
-- @return The data in its serialized form (string)
|
||||
function AceSerializer:Serialize(...)
|
||||
local nres = 1
|
||||
|
||||
for i=1,select("#", ...) do
|
||||
local v = select(i, ...)
|
||||
nres = SerializeValue(v, serializeTbl, nres)
|
||||
end
|
||||
|
||||
serializeTbl[nres+1] = "^^" -- "^^" = End of serialized data
|
||||
|
||||
return tconcat(serializeTbl, "", 1, nres+1)
|
||||
end
|
||||
|
||||
-- Deserialization functions
|
||||
local function DeserializeStringHelper(escape)
|
||||
if escape<"~\122" then
|
||||
return strchar(strbyte(escape,2,2)-64)
|
||||
elseif escape=="~\122" then -- v3 / ticket 115: special case encode since 30+64=94 ("^") - OOPS.
|
||||
return "\030"
|
||||
elseif escape=="~\123" then
|
||||
return "\127"
|
||||
elseif escape=="~\124" then
|
||||
return "\126"
|
||||
elseif escape=="~\125" then
|
||||
return "\94"
|
||||
end
|
||||
error("DeserializeStringHelper got called for '"..escape.."'?!?") -- can't be reached unless regex is screwed up
|
||||
end
|
||||
|
||||
local function DeserializeNumberHelper(number)
|
||||
if number == serNaN then
|
||||
return 0/0
|
||||
elseif number == serNegInf then
|
||||
return -1/0
|
||||
elseif number == serInf then
|
||||
return 1/0
|
||||
else
|
||||
return tonumber(number)
|
||||
end
|
||||
end
|
||||
|
||||
-- DeserializeValue: worker function for :Deserialize()
|
||||
-- It works in two modes:
|
||||
-- Main (top-level) mode: Deserialize a list of values and return them all
|
||||
-- Recursive (table) mode: Deserialize only a single value (_may_ of course be another table with lots of subvalues in it)
|
||||
--
|
||||
-- The function _always_ works recursively due to having to build a list of values to return
|
||||
--
|
||||
-- Callers are expected to pcall(DeserializeValue) to trap errors
|
||||
|
||||
local function DeserializeValue(iter,single,ctl,data)
|
||||
|
||||
if not single then
|
||||
ctl,data = iter()
|
||||
end
|
||||
|
||||
if not ctl then
|
||||
error("Supplied data misses AceSerializer terminator ('^^')")
|
||||
end
|
||||
|
||||
if ctl=="^^" then
|
||||
-- ignore extraneous data
|
||||
return
|
||||
end
|
||||
|
||||
local res
|
||||
|
||||
if ctl=="^S" then
|
||||
res = gsub(data, "~.", DeserializeStringHelper)
|
||||
elseif ctl=="^N" then
|
||||
res = DeserializeNumberHelper(data)
|
||||
if not res then
|
||||
error("Invalid serialized number: '"..tostring(data).."'")
|
||||
end
|
||||
elseif ctl=="^F" then -- ^F<mantissa>^f<exponent>
|
||||
local ctl2,e = iter()
|
||||
if ctl2~="^f" then
|
||||
error("Invalid serialized floating-point number, expected '^f', not '"..tostring(ctl2).."'")
|
||||
end
|
||||
local m=tonumber(data)
|
||||
e=tonumber(e)
|
||||
if not (m and e) then
|
||||
error("Invalid serialized floating-point number, expected mantissa and exponent, got '"..tostring(m).."' and '"..tostring(e).."'")
|
||||
end
|
||||
res = m*(2^e)
|
||||
elseif ctl=="^B" then -- yeah yeah ignore data portion
|
||||
res = true
|
||||
elseif ctl=="^b" then -- yeah yeah ignore data portion
|
||||
res = false
|
||||
elseif ctl=="^Z" then -- yeah yeah ignore data portion
|
||||
res = nil
|
||||
elseif ctl=="^T" then
|
||||
-- ignore ^T's data, future extensibility?
|
||||
res = {}
|
||||
local k,v
|
||||
while true do
|
||||
ctl,data = iter()
|
||||
if ctl=="^t" then break end -- ignore ^t's data
|
||||
k = DeserializeValue(iter,true,ctl,data)
|
||||
if k==nil then
|
||||
error("Invalid AceSerializer table format (no table end marker)")
|
||||
end
|
||||
ctl,data = iter()
|
||||
v = DeserializeValue(iter,true,ctl,data)
|
||||
if v==nil then
|
||||
error("Invalid AceSerializer table format (no table end marker)")
|
||||
end
|
||||
res[k]=v
|
||||
end
|
||||
else
|
||||
error("Invalid AceSerializer control code '"..ctl.."'")
|
||||
end
|
||||
|
||||
if not single then
|
||||
return res,DeserializeValue(iter)
|
||||
else
|
||||
return res
|
||||
end
|
||||
end
|
||||
|
||||
--- Deserializes the data into its original values.
|
||||
-- Accepts serialized data, ignoring all control characters and whitespace.
|
||||
-- @param str The serialized data (from :Serialize)
|
||||
-- @return true followed by a list of values, OR false followed by an error message
|
||||
function AceSerializer:Deserialize(str)
|
||||
str = gsub(str, "[%c ]", "") -- ignore all control characters; nice for embedding in email and stuff
|
||||
|
||||
local iter = gmatch(str, "(^.)([^^]*)") -- Any ^x followed by string of non-^
|
||||
local ctl,data = iter()
|
||||
if not ctl or ctl~="^1" then
|
||||
-- we purposefully ignore the data portion of the start code, it can be used as an extension mechanism
|
||||
return false, "Supplied data is not AceSerializer data (rev 1)"
|
||||
end
|
||||
|
||||
return pcall(DeserializeValue, iter)
|
||||
end
|
||||
|
||||
|
||||
----------------------------------------
|
||||
-- Base library stuff
|
||||
----------------------------------------
|
||||
|
||||
AceSerializer.internals = { -- for test scripts
|
||||
SerializeValue = SerializeValue,
|
||||
SerializeStringHelper = SerializeStringHelper,
|
||||
}
|
||||
|
||||
local mixins = {
|
||||
"Serialize",
|
||||
"Deserialize",
|
||||
}
|
||||
|
||||
AceSerializer.embeds = AceSerializer.embeds or {}
|
||||
|
||||
function AceSerializer:Embed(target)
|
||||
for k, v in pairs(mixins) do
|
||||
target[v] = self[v]
|
||||
end
|
||||
self.embeds[target] = true
|
||||
return target
|
||||
end
|
||||
|
||||
-- Update embeds
|
||||
for target, v in pairs(AceSerializer.embeds) do
|
||||
AceSerializer:Embed(target)
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceSerializer-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -0,0 +1,239 @@
|
||||
--[[ $Id: CallbackHandler-1.0.lua 3 2008-09-29 16:54:20Z nevcairiel $ ]]
|
||||
local MAJOR, MINOR = "CallbackHandler-1.0", 3
|
||||
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not CallbackHandler then return end -- No upgrade needed
|
||||
|
||||
local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
|
||||
|
||||
local type = type
|
||||
local pcall = pcall
|
||||
local pairs = pairs
|
||||
local assert = assert
|
||||
local concat = table.concat
|
||||
local loadstring = loadstring
|
||||
local next = next
|
||||
local select = select
|
||||
local type = type
|
||||
local xpcall = xpcall
|
||||
|
||||
local function errorhandler(err)
|
||||
return geterrorhandler()(err)
|
||||
end
|
||||
|
||||
local function CreateDispatcher(argCount)
|
||||
local code = [[
|
||||
local next, xpcall, eh = ...
|
||||
|
||||
local method, ARGS
|
||||
local function call() method(ARGS) end
|
||||
|
||||
local function dispatch(handlers, ...)
|
||||
local index
|
||||
index, method = next(handlers)
|
||||
if not method then return end
|
||||
local OLD_ARGS = ARGS
|
||||
ARGS = ...
|
||||
repeat
|
||||
xpcall(call, eh)
|
||||
index, method = next(handlers, index)
|
||||
until not method
|
||||
ARGS = OLD_ARGS
|
||||
end
|
||||
|
||||
return dispatch
|
||||
]]
|
||||
|
||||
local ARGS, OLD_ARGS = {}, {}
|
||||
for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
|
||||
code = code:gsub("OLD_ARGS", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(ARGS, ", "))
|
||||
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
|
||||
end
|
||||
|
||||
local Dispatchers = setmetatable({}, {__index=function(self, argCount)
|
||||
local dispatcher = CreateDispatcher(argCount)
|
||||
rawset(self, argCount, dispatcher)
|
||||
return dispatcher
|
||||
end})
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- CallbackHandler:New
|
||||
--
|
||||
-- target - target object to embed public APIs in
|
||||
-- RegisterName - name of the callback registration API, default "RegisterCallback"
|
||||
-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
|
||||
-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
|
||||
|
||||
function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused)
|
||||
-- TODO: Remove this after beta has gone out
|
||||
assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused")
|
||||
|
||||
RegisterName = RegisterName or "RegisterCallback"
|
||||
UnregisterName = UnregisterName or "UnregisterCallback"
|
||||
if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
|
||||
UnregisterAllName = "UnregisterAllCallbacks"
|
||||
end
|
||||
|
||||
-- we declare all objects and exported APIs inside this closure to quickly gain access
|
||||
-- to e.g. function names, the "target" parameter, etc
|
||||
|
||||
|
||||
-- Create the registry object
|
||||
local events = setmetatable({}, meta)
|
||||
local registry = { recurse=0, events=events }
|
||||
|
||||
-- registry:Fire() - fires the given event/message into the registry
|
||||
function registry:Fire(eventname, ...)
|
||||
if not rawget(events, eventname) or not next(events[eventname]) then return end
|
||||
local oldrecurse = registry.recurse
|
||||
registry.recurse = oldrecurse + 1
|
||||
|
||||
Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
|
||||
|
||||
registry.recurse = oldrecurse
|
||||
|
||||
if registry.insertQueue and oldrecurse==0 then
|
||||
-- Something in one of our callbacks wanted to register more callbacks; they got queued
|
||||
for eventname,callbacks in pairs(registry.insertQueue) do
|
||||
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
|
||||
for self,func in pairs(callbacks) do
|
||||
events[eventname][self] = func
|
||||
-- fire OnUsed callback?
|
||||
if first and registry.OnUsed then
|
||||
registry.OnUsed(registry, target, eventname)
|
||||
first = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
registry.insertQueue = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Registration of a callback, handles:
|
||||
-- self["method"], leads to self["method"](self, ...)
|
||||
-- self with function ref, leads to functionref(...)
|
||||
-- "addonId" (instead of self) with function ref, leads to functionref(...)
|
||||
-- all with an optional arg, which, if present, gets passed as first argument (after self if present)
|
||||
target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
|
||||
if type(eventname) ~= "string" then
|
||||
error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
|
||||
end
|
||||
|
||||
method = method or eventname
|
||||
|
||||
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
|
||||
|
||||
if type(method) ~= "string" and type(method) ~= "function" then
|
||||
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
|
||||
end
|
||||
|
||||
local regfunc
|
||||
|
||||
if type(method) == "string" then
|
||||
-- self["method"] calling style
|
||||
if type(self) ~= "table" then
|
||||
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
|
||||
elseif self==target then
|
||||
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
|
||||
elseif type(self[method]) ~= "function" then
|
||||
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
|
||||
end
|
||||
|
||||
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
|
||||
local arg=select(1,...)
|
||||
regfunc = function(...) self[method](self,arg,...) end
|
||||
else
|
||||
regfunc = function(...) self[method](self,...) end
|
||||
end
|
||||
else
|
||||
-- function ref with self=object or self="addonId"
|
||||
if type(self)~="table" and type(self)~="string" then
|
||||
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2)
|
||||
end
|
||||
|
||||
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
|
||||
local arg=select(1,...)
|
||||
regfunc = function(...) method(arg,...) end
|
||||
else
|
||||
regfunc = method
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if events[eventname][self] or registry.recurse<1 then
|
||||
-- if registry.recurse<1 then
|
||||
-- we're overwriting an existing entry, or not currently recursing. just set it.
|
||||
events[eventname][self] = regfunc
|
||||
-- fire OnUsed callback?
|
||||
if registry.OnUsed and first then
|
||||
registry.OnUsed(registry, target, eventname)
|
||||
end
|
||||
else
|
||||
-- we're currently processing a callback in this registry, so delay the registration of this new entry!
|
||||
-- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
|
||||
registry.insertQueue = registry.insertQueue or setmetatable({},meta)
|
||||
registry.insertQueue[eventname][self] = regfunc
|
||||
end
|
||||
end
|
||||
|
||||
-- Unregister a callback
|
||||
target[UnregisterName] = function(self, eventname)
|
||||
if not self or self==target then
|
||||
error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
|
||||
end
|
||||
if type(eventname) ~= "string" then
|
||||
error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
|
||||
end
|
||||
if rawget(events, eventname) and events[eventname][self] then
|
||||
events[eventname][self] = nil
|
||||
-- Fire OnUnused callback?
|
||||
if registry.OnUnused and not next(events[eventname]) then
|
||||
registry.OnUnused(registry, target, eventname)
|
||||
end
|
||||
end
|
||||
if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
|
||||
registry.insertQueue[eventname][self] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- OPTIONAL: Unregister all callbacks for given selfs/addonIds
|
||||
if UnregisterAllName then
|
||||
target[UnregisterAllName] = function(...)
|
||||
if select("#",...)<1 then
|
||||
error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
|
||||
end
|
||||
if select("#",...)==1 and ...==target then
|
||||
error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
|
||||
end
|
||||
|
||||
|
||||
for i=1,select("#",...) do
|
||||
local self = select(i,...)
|
||||
if registry.insertQueue then
|
||||
for eventname, callbacks in pairs(registry.insertQueue) do
|
||||
if callbacks[self] then
|
||||
callbacks[self] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
for eventname, callbacks in pairs(events) do
|
||||
if callbacks[self] then
|
||||
callbacks[self] = nil
|
||||
-- Fire OnUnused callback?
|
||||
if registry.OnUnused and not next(callbacks) then
|
||||
registry.OnUnused(registry, target, eventname)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return registry
|
||||
end
|
||||
|
||||
|
||||
-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
|
||||
-- try to upgrade old implicit embeds since the system is selfcontained and
|
||||
-- relies on closures to work.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="CallbackHandler-1.0.lua"/>
|
||||
</Ui>
|
||||
@@ -0,0 +1,799 @@
|
||||
--[[
|
||||
Name: AceLibrary
|
||||
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: 1091 $
|
||||
Website: http://www.wowace.com/
|
||||
Documentation: http://www.wowace.com/index.php/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
|
||||
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 = 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
|
||||
|
||||
-- @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 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 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
|
||||
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_argCheck, old_pcall
|
||||
if isAceLibrary then
|
||||
self = instance
|
||||
AceLibrary = instance
|
||||
|
||||
old_error = instance.error
|
||||
old_argCheck = instance.argCheck
|
||||
old_pcall = instance.pcall
|
||||
|
||||
self.error = error
|
||||
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 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 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)
|
||||
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)
|
||||
@@ -0,0 +1,15 @@
|
||||
## Interface: 30300
|
||||
## X-Curse-Packaged-Version: r1101
|
||||
## 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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
## Interface: 20400
|
||||
## LoadOnDemand: 1
|
||||
## Title: Lib: Dewdrop-2.0
|
||||
## Notes: A library to provide a clean dropdown menu interface.
|
||||
## Author: ckknight
|
||||
## eMail: ckknight@gmail.com
|
||||
## X-Category: Library
|
||||
## OptionalDeps: Ace2
|
||||
## X-AceLibrary-Dewdrop-2.0: true
|
||||
## X-License: LGPL v2.1
|
||||
## X-Curse-Packaged-Version: r321
|
||||
## X-Curse-Project-Name: DewdropLib
|
||||
## X-Curse-Project-ID: dewdroplib
|
||||
## X-Curse-Repository-ID: wow/dewdroplib/mainline
|
||||
|
||||
embeds.xml
|
||||
|
||||
Dewdrop-2.0\Dewdrop-2.0.lua
|
||||
@@ -0,0 +1,15 @@
|
||||
Copyright (C) 2006-2007 ckknight
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
@@ -0,0 +1,6 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
|
||||
<Script file="AceLibrary\AceLibrary.lua"/>
|
||||
|
||||
</Ui>
|
||||
@@ -0,0 +1,201 @@
|
||||
--[[
|
||||
|
||||
****************************************************************************************
|
||||
LibAboutPanel
|
||||
|
||||
File date: 2009-06-23T02:04:30Z
|
||||
Project version: v1.43
|
||||
|
||||
Author: Tekkub, Ackis
|
||||
|
||||
****************************************************************************************
|
||||
|
||||
]]--
|
||||
|
||||
local lib, oldminor = LibStub:NewLibrary("LibAboutPanel", 2)
|
||||
if not lib then return end
|
||||
|
||||
function lib.new(parent, addonname)
|
||||
local frame = CreateFrame("Frame", nil, UIParent)
|
||||
frame.name, frame.parent, frame.addonname = not parent and gsub(addonname," ","") or "About", parent, gsub(addonname," ","") -- Remove spaces from addonname because GetMetadata doesn't like that
|
||||
frame:Hide()
|
||||
frame:SetScript("OnShow", lib.OnShow)
|
||||
InterfaceOptions_AddCategory(frame)
|
||||
return frame
|
||||
end
|
||||
|
||||
local GAME_LOCALE = GetLocale()
|
||||
|
||||
local L = {}
|
||||
|
||||
-- frFR
|
||||
if GAME_LOCALE == "frFR" then
|
||||
L["About"] = "à propos de"
|
||||
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy"
|
||||
-- deDE
|
||||
elseif GAME_LOCALE == "deDE" then
|
||||
L["About"] = "Über"
|
||||
L["Click and press Ctrl-C to copy"] = "Klicken und Strg-C drücken zum kopieren"
|
||||
-- esES
|
||||
elseif GAME_LOCALE == "esES" then
|
||||
L["About"] = "Acerca de"
|
||||
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy"
|
||||
-- esMX
|
||||
elseif GAME_LOCALE == "esMX" then
|
||||
L["About"] = "Sobre"
|
||||
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy"
|
||||
-- koKR
|
||||
elseif GAME_LOCALE == "koKR" then
|
||||
L["About"] = "대하여"
|
||||
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy"
|
||||
-- ruRU
|
||||
elseif GAME_LOCALE == "ruRU" then
|
||||
L["About"] = "Об аддоне"
|
||||
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy"
|
||||
-- zhCN
|
||||
elseif GAME_LOCALE == "zhCN" then
|
||||
L["About"] = "关于"
|
||||
L["Click and press Ctrl-C to copy"] = "点击并 Ctrl-C 复制"
|
||||
-- zhTW
|
||||
elseif GAME_LOCALE == "zhTW" then
|
||||
L["About"] = "關於"
|
||||
L["Click and press Ctrl-C to copy"] = "點擊並 Ctrl-C 復制"
|
||||
-- enUS and non-localized
|
||||
else
|
||||
L["About"] ="About"
|
||||
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy"
|
||||
end
|
||||
|
||||
local editbox = CreateFrame('EditBox', nil, UIParent)
|
||||
editbox:Hide()
|
||||
editbox:SetAutoFocus(true)
|
||||
editbox:SetHeight(32)
|
||||
editbox:SetFontObject('GameFontHighlightSmall')
|
||||
lib.editbox = editbox
|
||||
|
||||
local left = editbox:CreateTexture(nil, "BACKGROUND")
|
||||
left:SetWidth(8) left:SetHeight(20)
|
||||
left:SetPoint("LEFT", -5, 0)
|
||||
left:SetTexture("Interface\\Common\\Common-Input-Border")
|
||||
left:SetTexCoord(0, 0.0625, 0, 0.625)
|
||||
|
||||
local right = editbox:CreateTexture(nil, "BACKGROUND")
|
||||
right:SetWidth(8) right:SetHeight(20)
|
||||
right:SetPoint("RIGHT", 0, 0)
|
||||
right:SetTexture("Interface\\Common\\Common-Input-Border")
|
||||
right:SetTexCoord(0.9375, 1, 0, 0.625)
|
||||
|
||||
local center = editbox:CreateTexture(nil, "BACKGROUND")
|
||||
center:SetHeight(20)
|
||||
center:SetPoint("RIGHT", right, "LEFT", 0, 0)
|
||||
center:SetPoint("LEFT", left, "RIGHT", 0, 0)
|
||||
center:SetTexture("Interface\\Common\\Common-Input-Border")
|
||||
center:SetTexCoord(0.0625, 0.9375, 0, 0.625)
|
||||
|
||||
editbox:SetScript("OnEscapePressed", editbox.ClearFocus)
|
||||
editbox:SetScript("OnEnterPressed", editbox.ClearFocus)
|
||||
editbox:SetScript("OnEditFocusLost", editbox.Hide)
|
||||
editbox:SetScript("OnEditFocusGained", editbox.HighlightText)
|
||||
editbox:SetScript("OnTextChanged", function(self)
|
||||
self:SetText(self:GetParent().val)
|
||||
self:HighlightText()
|
||||
end)
|
||||
|
||||
|
||||
function lib.OpenEditbox(self)
|
||||
editbox:SetText(self.val)
|
||||
editbox:SetParent(self)
|
||||
editbox:SetPoint("LEFT", self)
|
||||
editbox:SetPoint("RIGHT", self)
|
||||
editbox:Show()
|
||||
end
|
||||
|
||||
|
||||
local fields = {"Version", "Author", "X-Category", "X-License", "X-Email", "Email", "eMail", "X-Website", "X-Credits", "X-Localizations", "X-Donate"}
|
||||
local haseditbox = {["X-Website"] = true, ["X-Email"] = true, ["X-Donate"] = true, ["Email"] = true, ["eMail"] = true}
|
||||
|
||||
local function HideTooltip() GameTooltip:Hide() end
|
||||
|
||||
local function ShowTooltip(self)
|
||||
GameTooltip:SetOwner(self, "ANCHOR_TOPRIGHT")
|
||||
GameTooltip:SetText(L["Click and press Ctrl-C to copy"])
|
||||
--GameTooltip:SetText("Click and press Ctrl-C to copy")
|
||||
end
|
||||
|
||||
function lib.OnShow(frame)
|
||||
|
||||
local notefield = "Notes"
|
||||
|
||||
if (GAME_LOCALE ~= "enUS") then
|
||||
notefield = notefield .. "-" .. GAME_LOCALE
|
||||
end
|
||||
|
||||
-- Get the localized version of notes if it exists or fall back to the english one.
|
||||
local notes = GetAddOnMetadata(frame.addonname, notefield) or GetAddOnMetadata(frame.addonname, "Notes")
|
||||
|
||||
local title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
|
||||
title:SetPoint("TOPLEFT", 16, -16)
|
||||
title:SetText(frame.parent and (frame.parent.." - " .. L["About"]) or frame.name)
|
||||
|
||||
local subtitle = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
subtitle:SetHeight(32)
|
||||
subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8)
|
||||
subtitle:SetPoint("RIGHT", frame, -32, 0)
|
||||
subtitle:SetNonSpaceWrap(true)
|
||||
subtitle:SetJustifyH("LEFT")
|
||||
subtitle:SetJustifyV("TOP")
|
||||
subtitle:SetText(notes)
|
||||
|
||||
local anchor
|
||||
for _,field in pairs(fields) do
|
||||
local val = GetAddOnMetadata(frame.addonname, field)
|
||||
if val then
|
||||
local title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
title:SetWidth(75)
|
||||
if not anchor then title:SetPoint("TOPLEFT", subtitle, "BOTTOMLEFT", -2, -12)
|
||||
else title:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -10) end
|
||||
title:SetJustifyH("RIGHT")
|
||||
title:SetText(field:gsub("X%-", ""))
|
||||
|
||||
local detail = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
|
||||
detail:SetHeight(32)
|
||||
detail:SetPoint("LEFT", title, "RIGHT", 4, 0)
|
||||
detail:SetPoint("RIGHT", frame, -16, 0)
|
||||
detail:SetJustifyH("LEFT")
|
||||
|
||||
if (field == "Author") then
|
||||
local authorservername = GetAddOnMetadata(frame.addonname, "X-Author-Server")
|
||||
local authorfaction = GetAddOnMetadata(frame.addonname, "X-Author-Faction")
|
||||
|
||||
if authorservername and authorfaction then
|
||||
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. val .. " on " .. authorservername .. " (" .. authorfaction .. ")")
|
||||
elseif authorservername and not authorfaction then
|
||||
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. val .. " on " .. authorservername)
|
||||
elseif not authorservername and authorfaction then
|
||||
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. val .. " (" .. authorfaction .. ")")
|
||||
else
|
||||
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. val)
|
||||
end
|
||||
elseif (field == "Version") then
|
||||
local addonversion = GetAddOnMetadata(frame.addonname, field)
|
||||
-- Remove @project-revision@ and replace it with Repository
|
||||
addonversion = string.gsub(addonversion,"@project.revision@","Repository")
|
||||
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. addonversion)
|
||||
else
|
||||
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. val)
|
||||
end
|
||||
|
||||
if haseditbox[field] then
|
||||
local button = CreateFrame("Button", nil, frame)
|
||||
button:SetAllPoints(detail)
|
||||
button.val = val
|
||||
button:SetScript("OnClick", lib.OpenEditbox)
|
||||
button:SetScript("OnEnter", ShowTooltip)
|
||||
button:SetScript("OnLeave", HideTooltip)
|
||||
end
|
||||
|
||||
anchor = title
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
## Title: Lib: AboutPanel
|
||||
## X-Curse-Packaged-Version: v1.43
|
||||
## X-Curse-Project-Name: LibAboutPanel
|
||||
## X-Curse-Project-ID: libaboutpanel
|
||||
## X-Curse-Repository-ID: wow/libaboutpanel/mainline
|
||||
|
||||
## Notes: Adds an about panel to interface options.
|
||||
|
||||
## Author: Ackis
|
||||
## eMail: ackis AT shaw DOT ca
|
||||
##X-Author-Faction = Alliance
|
||||
##X-Author-Server = Thunderlord US
|
||||
## X-Donate: http://www.curseforge.com/projects/libaboutpanel/#w_donations
|
||||
|
||||
## Interface: 30300
|
||||
## Version: 1.43
|
||||
## X-Revision: @project-revision@
|
||||
## X-Date: 2009-12-18T22:27:31Z
|
||||
|
||||
## X-Category: Libraries
|
||||
## X-Localizations: enUS
|
||||
## X-Website: http://www.wowwiki.com/LibAboutPanel
|
||||
## X-Feedback: http://wow.curse.com/downloads/wow-addons/details/libaboutpanel.aspx
|
||||
|
||||
## Dependencies:
|
||||
## X-Embeds: LibStub, CallbackHandler-1.0
|
||||
## OptionalDeps: LibStub, CallbackHandler-1.0
|
||||
## DefaultState: Enabled
|
||||
## LoadOnDemand: 0
|
||||
|
||||
#@no-lib-strip@
|
||||
libs\LibStub\LibStub.lua
|
||||
libs\CallbackHandler-1.0\CallbackHandler-1.0.xml
|
||||
#@end-no-lib-strip@
|
||||
|
||||
LibAboutPanel.lua
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="LibAboutPanel.lua" />
|
||||
</Ui>
|
||||
@@ -0,0 +1,240 @@
|
||||
--[[ $Id: CallbackHandler-1.0.lua 14 2010-08-09 00:43:38Z mikk $ ]]
|
||||
local MAJOR, MINOR = "CallbackHandler-1.0", 6
|
||||
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not CallbackHandler then return end -- No upgrade needed
|
||||
|
||||
local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
|
||||
|
||||
-- Lua APIs
|
||||
local tconcat = table.concat
|
||||
local assert, error, loadstring = assert, error, loadstring
|
||||
local setmetatable, rawset, rawget = setmetatable, rawset, rawget
|
||||
local next, select, pairs, type, tostring = next, select, pairs, type, tostring
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: geterrorhandler
|
||||
|
||||
local xpcall = xpcall
|
||||
|
||||
local function errorhandler(err)
|
||||
return geterrorhandler()(err)
|
||||
end
|
||||
|
||||
local function CreateDispatcher(argCount)
|
||||
local code = [[
|
||||
local next, xpcall, eh = ...
|
||||
|
||||
local method, ARGS
|
||||
local function call() method(ARGS) end
|
||||
|
||||
local function dispatch(handlers, ...)
|
||||
local index
|
||||
index, method = next(handlers)
|
||||
if not method then return end
|
||||
local OLD_ARGS = ARGS
|
||||
ARGS = ...
|
||||
repeat
|
||||
xpcall(call, eh)
|
||||
index, method = next(handlers, index)
|
||||
until not method
|
||||
ARGS = OLD_ARGS
|
||||
end
|
||||
|
||||
return dispatch
|
||||
]]
|
||||
|
||||
local ARGS, OLD_ARGS = {}, {}
|
||||
for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
|
||||
code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", "))
|
||||
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
|
||||
end
|
||||
|
||||
local Dispatchers = setmetatable({}, {__index=function(self, argCount)
|
||||
local dispatcher = CreateDispatcher(argCount)
|
||||
rawset(self, argCount, dispatcher)
|
||||
return dispatcher
|
||||
end})
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
-- CallbackHandler:New
|
||||
--
|
||||
-- target - target object to embed public APIs in
|
||||
-- RegisterName - name of the callback registration API, default "RegisterCallback"
|
||||
-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
|
||||
-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
|
||||
|
||||
function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused)
|
||||
-- TODO: Remove this after beta has gone out
|
||||
assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused")
|
||||
|
||||
RegisterName = RegisterName or "RegisterCallback"
|
||||
UnregisterName = UnregisterName or "UnregisterCallback"
|
||||
if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
|
||||
UnregisterAllName = "UnregisterAllCallbacks"
|
||||
end
|
||||
|
||||
-- we declare all objects and exported APIs inside this closure to quickly gain access
|
||||
-- to e.g. function names, the "target" parameter, etc
|
||||
|
||||
|
||||
-- Create the registry object
|
||||
local events = setmetatable({}, meta)
|
||||
local registry = { recurse=0, events=events }
|
||||
|
||||
-- registry:Fire() - fires the given event/message into the registry
|
||||
function registry:Fire(eventname, ...)
|
||||
if not rawget(events, eventname) or not next(events[eventname]) then return end
|
||||
local oldrecurse = registry.recurse
|
||||
registry.recurse = oldrecurse + 1
|
||||
|
||||
Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
|
||||
|
||||
registry.recurse = oldrecurse
|
||||
|
||||
if registry.insertQueue and oldrecurse==0 then
|
||||
-- Something in one of our callbacks wanted to register more callbacks; they got queued
|
||||
for eventname,callbacks in pairs(registry.insertQueue) do
|
||||
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
|
||||
for self,func in pairs(callbacks) do
|
||||
events[eventname][self] = func
|
||||
-- fire OnUsed callback?
|
||||
if first and registry.OnUsed then
|
||||
registry.OnUsed(registry, target, eventname)
|
||||
first = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
registry.insertQueue = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Registration of a callback, handles:
|
||||
-- self["method"], leads to self["method"](self, ...)
|
||||
-- self with function ref, leads to functionref(...)
|
||||
-- "addonId" (instead of self) with function ref, leads to functionref(...)
|
||||
-- all with an optional arg, which, if present, gets passed as first argument (after self if present)
|
||||
target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
|
||||
if type(eventname) ~= "string" then
|
||||
error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
|
||||
end
|
||||
|
||||
method = method or eventname
|
||||
|
||||
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
|
||||
|
||||
if type(method) ~= "string" and type(method) ~= "function" then
|
||||
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
|
||||
end
|
||||
|
||||
local regfunc
|
||||
|
||||
if type(method) == "string" then
|
||||
-- self["method"] calling style
|
||||
if type(self) ~= "table" then
|
||||
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
|
||||
elseif self==target then
|
||||
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
|
||||
elseif type(self[method]) ~= "function" then
|
||||
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
|
||||
end
|
||||
|
||||
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
|
||||
local arg=select(1,...)
|
||||
regfunc = function(...) self[method](self,arg,...) end
|
||||
else
|
||||
regfunc = function(...) self[method](self,...) end
|
||||
end
|
||||
else
|
||||
-- function ref with self=object or self="addonId" or self=thread
|
||||
if type(self)~="table" and type(self)~="string" and type(self)~="thread" then
|
||||
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2)
|
||||
end
|
||||
|
||||
if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
|
||||
local arg=select(1,...)
|
||||
regfunc = function(...) method(arg,...) end
|
||||
else
|
||||
regfunc = method
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if events[eventname][self] or registry.recurse<1 then
|
||||
-- if registry.recurse<1 then
|
||||
-- we're overwriting an existing entry, or not currently recursing. just set it.
|
||||
events[eventname][self] = regfunc
|
||||
-- fire OnUsed callback?
|
||||
if registry.OnUsed and first then
|
||||
registry.OnUsed(registry, target, eventname)
|
||||
end
|
||||
else
|
||||
-- we're currently processing a callback in this registry, so delay the registration of this new entry!
|
||||
-- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
|
||||
registry.insertQueue = registry.insertQueue or setmetatable({},meta)
|
||||
registry.insertQueue[eventname][self] = regfunc
|
||||
end
|
||||
end
|
||||
|
||||
-- Unregister a callback
|
||||
target[UnregisterName] = function(self, eventname)
|
||||
if not self or self==target then
|
||||
error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
|
||||
end
|
||||
if type(eventname) ~= "string" then
|
||||
error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
|
||||
end
|
||||
if rawget(events, eventname) and events[eventname][self] then
|
||||
events[eventname][self] = nil
|
||||
-- Fire OnUnused callback?
|
||||
if registry.OnUnused and not next(events[eventname]) then
|
||||
registry.OnUnused(registry, target, eventname)
|
||||
end
|
||||
end
|
||||
if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
|
||||
registry.insertQueue[eventname][self] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- OPTIONAL: Unregister all callbacks for given selfs/addonIds
|
||||
if UnregisterAllName then
|
||||
target[UnregisterAllName] = function(...)
|
||||
if select("#",...)<1 then
|
||||
error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
|
||||
end
|
||||
if select("#",...)==1 and ...==target then
|
||||
error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
|
||||
end
|
||||
|
||||
|
||||
for i=1,select("#",...) do
|
||||
local self = select(i,...)
|
||||
if registry.insertQueue then
|
||||
for eventname, callbacks in pairs(registry.insertQueue) do
|
||||
if callbacks[self] then
|
||||
callbacks[self] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
for eventname, callbacks in pairs(events) do
|
||||
if callbacks[self] then
|
||||
callbacks[self] = nil
|
||||
-- Fire OnUnused callback?
|
||||
if registry.OnUnused and not next(callbacks) then
|
||||
registry.OnUnused(registry, target, eventname)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return registry
|
||||
end
|
||||
|
||||
|
||||
-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
|
||||
-- try to upgrade old implicit embeds since the system is selfcontained and
|
||||
-- relies on closures to work.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="CallbackHandler-1.0.lua"/>
|
||||
</Ui>
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 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
|
||||
@@ -0,0 +1,13 @@
|
||||
## Interface: 20400
|
||||
## Title: Lib: LibStub
|
||||
## Notes: Universal Library Stub
|
||||
## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel
|
||||
## X-Website: http://jira.wowace.com/browse/LS
|
||||
## X-Category: Library
|
||||
## X-License: Public Domain
|
||||
## X-Curse-Packaged-Version: 1.0
|
||||
## X-Curse-Project-Name: LibStub
|
||||
## X-Curse-Project-ID: libstub
|
||||
## X-Curse-Repository-ID: wow/libstub/mainline
|
||||
|
||||
LibStub.lua
|
||||
@@ -0,0 +1,292 @@
|
||||
-- LibBabble-3.0 is hereby placed in the Public Domain
|
||||
-- Credits: ckknight
|
||||
local LIBBABBLE_MAJOR, LIBBABBLE_MINOR = "LibBabble-3.0", 2
|
||||
|
||||
local LibBabble = LibStub:NewLibrary(LIBBABBLE_MAJOR, LIBBABBLE_MINOR)
|
||||
if not LibBabble then
|
||||
return
|
||||
end
|
||||
|
||||
local data = LibBabble.data or {}
|
||||
for k,v in pairs(LibBabble) do
|
||||
LibBabble[k] = nil
|
||||
end
|
||||
LibBabble.data = data
|
||||
|
||||
local tablesToDB = {}
|
||||
for namespace, db in pairs(data) do
|
||||
for k,v in pairs(db) do
|
||||
tablesToDB[v] = db
|
||||
end
|
||||
end
|
||||
|
||||
local function warn(message)
|
||||
local _, ret = pcall(error, message, 3)
|
||||
geterrorhandler()(ret)
|
||||
end
|
||||
|
||||
local lookup_mt = { __index = function(self, key)
|
||||
local db = tablesToDB[self]
|
||||
local current_key = db.current[key]
|
||||
if current_key then
|
||||
self[key] = current_key
|
||||
return current_key
|
||||
end
|
||||
local base_key = db.base[key]
|
||||
local real_MAJOR_VERSION
|
||||
for k,v in pairs(data) do
|
||||
if v == db then
|
||||
real_MAJOR_VERSION = k
|
||||
break
|
||||
end
|
||||
end
|
||||
if not real_MAJOR_VERSION then
|
||||
real_MAJOR_VERSION = LIBBABBLE_MAJOR
|
||||
end
|
||||
if base_key then
|
||||
warn(("%s: Translation %q not found for locale %q"):format(real_MAJOR_VERSION, key, GetLocale()))
|
||||
rawset(self, key, base_key)
|
||||
return base_key
|
||||
end
|
||||
warn(("%s: Translation %q not found."):format(real_MAJOR_VERSION, key))
|
||||
rawset(self, key, key)
|
||||
return key
|
||||
end }
|
||||
|
||||
local function initLookup(module, lookup)
|
||||
local db = tablesToDB[module]
|
||||
for k in pairs(lookup) do
|
||||
lookup[k] = nil
|
||||
end
|
||||
setmetatable(lookup, lookup_mt)
|
||||
tablesToDB[lookup] = db
|
||||
db.lookup = lookup
|
||||
return lookup
|
||||
end
|
||||
|
||||
local function initReverse(module, reverse)
|
||||
local db = tablesToDB[module]
|
||||
for k in pairs(reverse) do
|
||||
reverse[k] = nil
|
||||
end
|
||||
for k,v in pairs(db.current) do
|
||||
reverse[v] = k
|
||||
end
|
||||
tablesToDB[reverse] = db
|
||||
db.reverse = reverse
|
||||
db.reverseIterators = nil
|
||||
return reverse
|
||||
end
|
||||
|
||||
local prototype = {}
|
||||
local prototype_mt = {__index = prototype}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will warn but allow the code to pass through.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local BL = B:GetLookupTable()
|
||||
assert(BL["Some english word"] == "Some localized word")
|
||||
DoSomething(BL["Some english word that doesn't exist"]) -- warning!
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
local lookup = db.lookup
|
||||
if lookup then
|
||||
return lookup
|
||||
end
|
||||
return initLookup(self, {})
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local B_has = B:GetUnstrictLookupTable()
|
||||
assert(B_has["Some english word"] == "Some localized word")
|
||||
assert(B_has["Some english word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetUnstrictLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return db.current
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
* This is useful for checking if the base (English) table has a key, even if the localized one does not have it registered.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local B_hasBase = B:GetBaseLookupTable()
|
||||
assert(B_hasBase["Some english word"] == "Some english word")
|
||||
assert(B_hasBase["Some english word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetBaseLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return db.base
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
* This will return only one English word that it maps to, if there are more than one to check, see :GetReverseIterator("word")
|
||||
Returns:
|
||||
A lookup table for localized to english words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local BR = B:GetReverseLookupTable()
|
||||
assert(BR["Some localized word"] == "Some english word")
|
||||
assert(BR["Some localized word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetReverseLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
local reverse = db.reverse
|
||||
if reverse then
|
||||
return reverse
|
||||
end
|
||||
return initReverse(self, {})
|
||||
end
|
||||
local blank = {}
|
||||
local weakVal = {__mode='v'}
|
||||
--[[---------------------------------------------------------------------------
|
||||
Arguments:
|
||||
string - the localized word to chek for.
|
||||
Returns:
|
||||
An iterator to traverse all English words that map to the given key
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
for word in B:GetReverseIterator("Some localized word") do
|
||||
DoSomething(word)
|
||||
end
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetReverseIterator(key)
|
||||
local db = tablesToDB[self]
|
||||
local reverseIterators = db.reverseIterators
|
||||
if not reverseIterators then
|
||||
reverseIterators = setmetatable({}, weakVal)
|
||||
db.reverseIterators = reverseIterators
|
||||
elseif reverseIterators[key] then
|
||||
return pairs(reverseIterators[key])
|
||||
end
|
||||
local t
|
||||
for k,v in pairs(db.current) do
|
||||
if v == key then
|
||||
if not t then
|
||||
t = {}
|
||||
end
|
||||
t[k] = true
|
||||
end
|
||||
end
|
||||
reverseIterators[key] = t or blank
|
||||
return pairs(reverseIterators[key])
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Returns:
|
||||
An iterator to traverse all translations English to localized.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
for english, localized in B:Iterate() do
|
||||
DoSomething(english, localized)
|
||||
end
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:Iterate()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return pairs(db.current)
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to set the base table
|
||||
function prototype:SetBaseTranslations(base)
|
||||
local db = tablesToDB[self]
|
||||
local oldBase = db.base
|
||||
if oldBase then
|
||||
for k in pairs(oldBase) do
|
||||
oldBase[k] = nil
|
||||
end
|
||||
for k, v in pairs(base) do
|
||||
oldBase[k] = v
|
||||
end
|
||||
base = oldBase
|
||||
else
|
||||
db.base = base
|
||||
end
|
||||
for k,v in pairs(base) do
|
||||
if v == true then
|
||||
base[k] = k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function init(module)
|
||||
local db = tablesToDB[module]
|
||||
if db.lookup then
|
||||
initLookup(module, db.lookup)
|
||||
end
|
||||
if db.reverse then
|
||||
initReverse(module, db.reverse)
|
||||
end
|
||||
db.reverseIterators = nil
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to set the current table. if current is true, use the base table.
|
||||
function prototype:SetCurrentTranslations(current)
|
||||
local db = tablesToDB[self]
|
||||
if current == true then
|
||||
db.current = db.base
|
||||
else
|
||||
local oldCurrent = db.current
|
||||
if oldCurrent then
|
||||
for k in pairs(oldCurrent) do
|
||||
oldCurrent[k] = nil
|
||||
end
|
||||
for k, v in pairs(current) do
|
||||
oldCurrent[k] = v
|
||||
end
|
||||
current = oldCurrent
|
||||
else
|
||||
db.current = current
|
||||
end
|
||||
end
|
||||
init(self)
|
||||
end
|
||||
|
||||
for namespace, db in pairs(data) do
|
||||
setmetatable(db.module, prototype_mt)
|
||||
init(db.module)
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to create a new namespace.
|
||||
function LibBabble:New(namespace, minor)
|
||||
local module, oldminor = LibStub:NewLibrary(namespace, minor)
|
||||
if not module then
|
||||
return
|
||||
end
|
||||
|
||||
if not oldminor then
|
||||
local db = {
|
||||
module = module,
|
||||
}
|
||||
data[namespace] = db
|
||||
tablesToDB[module] = db
|
||||
else
|
||||
for k,v in pairs(module) do
|
||||
module[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(module, prototype_mt)
|
||||
|
||||
return module
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
## Interface: 30300
|
||||
## LoadOnDemand: 1
|
||||
## Title: Lib: Babble-Boss-3.0
|
||||
## Notes: A library to help with localization of bosses.
|
||||
## Notes-zhCN: 为本地化服务的支持库[首领名称]
|
||||
## Notes-zhTW: 為本地化服務的函式庫[首領名稱]
|
||||
## Notes-deDE: BabbleLib ist eine Bibliothek, die bei der Lokalisierung helfen soll.
|
||||
## Notes-frFR: Une bibliothèque d'aide à la localisation.
|
||||
## Notes-esES: Una biblioteca para ayudar con las localizaciones.
|
||||
## Author: ckknight
|
||||
## X-eMail: ckknight@gmail.com
|
||||
## X-Category: Library
|
||||
## X-License: MIT
|
||||
## X-Curse-Packaged-Version: r296
|
||||
## X-Curse-Project-Name: LibBabble-Boss-3.0
|
||||
## X-Curse-Project-ID: libbabble-boss-3-0
|
||||
## X-Curse-Repository-ID: wow/libbabble-boss-3-0/mainline
|
||||
|
||||
LibStub\LibStub.lua
|
||||
lib.xml
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 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
|
||||
@@ -0,0 +1,5 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="LibBabble-3.0.lua" />
|
||||
<Script file="LibBabble-Boss-3.0.lua" />
|
||||
</Ui>
|
||||
@@ -0,0 +1,292 @@
|
||||
-- LibBabble-3.0 is hereby placed in the Public Domain
|
||||
-- Credits: ckknight
|
||||
local LIBBABBLE_MAJOR, LIBBABBLE_MINOR = "LibBabble-3.0", 2
|
||||
|
||||
local LibBabble = LibStub:NewLibrary(LIBBABBLE_MAJOR, LIBBABBLE_MINOR)
|
||||
if not LibBabble then
|
||||
return
|
||||
end
|
||||
|
||||
local data = LibBabble.data or {}
|
||||
for k,v in pairs(LibBabble) do
|
||||
LibBabble[k] = nil
|
||||
end
|
||||
LibBabble.data = data
|
||||
|
||||
local tablesToDB = {}
|
||||
for namespace, db in pairs(data) do
|
||||
for k,v in pairs(db) do
|
||||
tablesToDB[v] = db
|
||||
end
|
||||
end
|
||||
|
||||
local function warn(message)
|
||||
local _, ret = pcall(error, message, 3)
|
||||
geterrorhandler()(ret)
|
||||
end
|
||||
|
||||
local lookup_mt = { __index = function(self, key)
|
||||
local db = tablesToDB[self]
|
||||
local current_key = db.current[key]
|
||||
if current_key then
|
||||
self[key] = current_key
|
||||
return current_key
|
||||
end
|
||||
local base_key = db.base[key]
|
||||
local real_MAJOR_VERSION
|
||||
for k,v in pairs(data) do
|
||||
if v == db then
|
||||
real_MAJOR_VERSION = k
|
||||
break
|
||||
end
|
||||
end
|
||||
if not real_MAJOR_VERSION then
|
||||
real_MAJOR_VERSION = LIBBABBLE_MAJOR
|
||||
end
|
||||
if base_key then
|
||||
warn(("%s: Translation %q not found for locale %q"):format(real_MAJOR_VERSION, key, GetLocale()))
|
||||
rawset(self, key, base_key)
|
||||
return base_key
|
||||
end
|
||||
warn(("%s: Translation %q not found."):format(real_MAJOR_VERSION, key))
|
||||
rawset(self, key, key)
|
||||
return key
|
||||
end }
|
||||
|
||||
local function initLookup(module, lookup)
|
||||
local db = tablesToDB[module]
|
||||
for k in pairs(lookup) do
|
||||
lookup[k] = nil
|
||||
end
|
||||
setmetatable(lookup, lookup_mt)
|
||||
tablesToDB[lookup] = db
|
||||
db.lookup = lookup
|
||||
return lookup
|
||||
end
|
||||
|
||||
local function initReverse(module, reverse)
|
||||
local db = tablesToDB[module]
|
||||
for k in pairs(reverse) do
|
||||
reverse[k] = nil
|
||||
end
|
||||
for k,v in pairs(db.current) do
|
||||
reverse[v] = k
|
||||
end
|
||||
tablesToDB[reverse] = db
|
||||
db.reverse = reverse
|
||||
db.reverseIterators = nil
|
||||
return reverse
|
||||
end
|
||||
|
||||
local prototype = {}
|
||||
local prototype_mt = {__index = prototype}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will warn but allow the code to pass through.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local BL = B:GetLookupTable()
|
||||
assert(BL["Some english word"] == "Some localized word")
|
||||
DoSomething(BL["Some english word that doesn't exist"]) -- warning!
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
local lookup = db.lookup
|
||||
if lookup then
|
||||
return lookup
|
||||
end
|
||||
return initLookup(self, {})
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local B_has = B:GetUnstrictLookupTable()
|
||||
assert(B_has["Some english word"] == "Some localized word")
|
||||
assert(B_has["Some english word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetUnstrictLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return db.current
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
* This is useful for checking if the base (English) table has a key, even if the localized one does not have it registered.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local B_hasBase = B:GetBaseLookupTable()
|
||||
assert(B_hasBase["Some english word"] == "Some english word")
|
||||
assert(B_hasBase["Some english word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetBaseLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return db.base
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
* This will return only one English word that it maps to, if there are more than one to check, see :GetReverseIterator("word")
|
||||
Returns:
|
||||
A lookup table for localized to english words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local BR = B:GetReverseLookupTable()
|
||||
assert(BR["Some localized word"] == "Some english word")
|
||||
assert(BR["Some localized word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetReverseLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
local reverse = db.reverse
|
||||
if reverse then
|
||||
return reverse
|
||||
end
|
||||
return initReverse(self, {})
|
||||
end
|
||||
local blank = {}
|
||||
local weakVal = {__mode='v'}
|
||||
--[[---------------------------------------------------------------------------
|
||||
Arguments:
|
||||
string - the localized word to chek for.
|
||||
Returns:
|
||||
An iterator to traverse all English words that map to the given key
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
for word in B:GetReverseIterator("Some localized word") do
|
||||
DoSomething(word)
|
||||
end
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetReverseIterator(key)
|
||||
local db = tablesToDB[self]
|
||||
local reverseIterators = db.reverseIterators
|
||||
if not reverseIterators then
|
||||
reverseIterators = setmetatable({}, weakVal)
|
||||
db.reverseIterators = reverseIterators
|
||||
elseif reverseIterators[key] then
|
||||
return pairs(reverseIterators[key])
|
||||
end
|
||||
local t
|
||||
for k,v in pairs(db.current) do
|
||||
if v == key then
|
||||
if not t then
|
||||
t = {}
|
||||
end
|
||||
t[k] = true
|
||||
end
|
||||
end
|
||||
reverseIterators[key] = t or blank
|
||||
return pairs(reverseIterators[key])
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Returns:
|
||||
An iterator to traverse all translations English to localized.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
for english, localized in B:Iterate() do
|
||||
DoSomething(english, localized)
|
||||
end
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:Iterate()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return pairs(db.current)
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to set the base table
|
||||
function prototype:SetBaseTranslations(base)
|
||||
local db = tablesToDB[self]
|
||||
local oldBase = db.base
|
||||
if oldBase then
|
||||
for k in pairs(oldBase) do
|
||||
oldBase[k] = nil
|
||||
end
|
||||
for k, v in pairs(base) do
|
||||
oldBase[k] = v
|
||||
end
|
||||
base = oldBase
|
||||
else
|
||||
db.base = base
|
||||
end
|
||||
for k,v in pairs(base) do
|
||||
if v == true then
|
||||
base[k] = k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function init(module)
|
||||
local db = tablesToDB[module]
|
||||
if db.lookup then
|
||||
initLookup(module, db.lookup)
|
||||
end
|
||||
if db.reverse then
|
||||
initReverse(module, db.reverse)
|
||||
end
|
||||
db.reverseIterators = nil
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to set the current table. if current is true, use the base table.
|
||||
function prototype:SetCurrentTranslations(current)
|
||||
local db = tablesToDB[self]
|
||||
if current == true then
|
||||
db.current = db.base
|
||||
else
|
||||
local oldCurrent = db.current
|
||||
if oldCurrent then
|
||||
for k in pairs(oldCurrent) do
|
||||
oldCurrent[k] = nil
|
||||
end
|
||||
for k, v in pairs(current) do
|
||||
oldCurrent[k] = v
|
||||
end
|
||||
current = oldCurrent
|
||||
else
|
||||
db.current = current
|
||||
end
|
||||
end
|
||||
init(self)
|
||||
end
|
||||
|
||||
for namespace, db in pairs(data) do
|
||||
setmetatable(db.module, prototype_mt)
|
||||
init(db.module)
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to create a new namespace.
|
||||
function LibBabble:New(namespace, minor)
|
||||
local module, oldminor = LibStub:NewLibrary(namespace, minor)
|
||||
if not module then
|
||||
return
|
||||
end
|
||||
|
||||
if not oldminor then
|
||||
local db = {
|
||||
module = module,
|
||||
}
|
||||
data[namespace] = db
|
||||
tablesToDB[module] = db
|
||||
else
|
||||
for k,v in pairs(module) do
|
||||
module[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(module, prototype_mt)
|
||||
|
||||
return module
|
||||
end
|
||||
@@ -0,0 +1,799 @@
|
||||
--[[
|
||||
Name: LibBabble-Faction-3.0
|
||||
Revision: $Rev: 112 $
|
||||
Maintainers: ckknight, nevcairiel, Ackis
|
||||
Website: http://www.wowace.com/projects/libbabble-faction-3-0/
|
||||
Dependencies: None
|
||||
License: MIT
|
||||
]]
|
||||
|
||||
local MAJOR_VERSION = "LibBabble-Faction-3.0"
|
||||
local MINOR_VERSION = 90000 + tonumber(("$Rev: 112 $"):match("%d+"))
|
||||
|
||||
if not LibStub then error(MAJOR_VERSION .. " requires LibStub.") end
|
||||
local lib = LibStub("LibBabble-3.0"):New(MAJOR_VERSION, MINOR_VERSION)
|
||||
if not lib then return end
|
||||
|
||||
local GAME_LOCALE = GetLocale()
|
||||
|
||||
lib:SetBaseTranslations {
|
||||
Alliance = "Alliance",
|
||||
["Alliance Vanguard"] = "Alliance Vanguard",
|
||||
["Argent Crusade"] = "Argent Crusade",
|
||||
["Argent Dawn"] = "Argent Dawn",
|
||||
["Ashtongue Deathsworn"] = "Ashtongue Deathsworn",
|
||||
["Bloodsail Buccaneers"] = "Bloodsail Buccaneers",
|
||||
["Booty Bay"] = "Booty Bay",
|
||||
["Brood of Nozdormu"] = "Brood of Nozdormu",
|
||||
["Cenarion Circle"] = "Cenarion Circle",
|
||||
["Cenarion Expedition"] = "Cenarion Expedition",
|
||||
["Darkmoon Faire"] = "Darkmoon Faire",
|
||||
["Darkspear Trolls"] = "Darkspear Trolls",
|
||||
Darnassus = "Darnassus",
|
||||
Everlook = "Everlook",
|
||||
Exalted = "Exalted",
|
||||
Exodar = "Exodar",
|
||||
["Explorers' League"] = "Explorers' League",
|
||||
["Frenzyheart Tribe"] = "Frenzyheart Tribe",
|
||||
Friendly = "Friendly",
|
||||
["Frostwolf Clan"] = "Frostwolf Clan",
|
||||
Gadgetzan = "Gadgetzan",
|
||||
["Gelkis Clan Centaur"] = "Gelkis Clan Centaur",
|
||||
["Gnomeregan Exiles"] = "Gnomeregan Exiles",
|
||||
Honored = "Honored",
|
||||
["Honor Hold"] = "Honor Hold",
|
||||
Horde = "Horde",
|
||||
["Horde Expedition"] = "Horde Expedition",
|
||||
["Hydraxian Waterlords"] = "Hydraxian Waterlords",
|
||||
Ironforge = "Ironforge",
|
||||
["Keepers of Time"] = "Keepers of Time",
|
||||
["Kirin Tor"] = "Kirin Tor",
|
||||
["Knights of the Ebon Blade"] = "Knights of the Ebon Blade",
|
||||
Kurenai = "Kurenai",
|
||||
["Lower City"] = "Lower City",
|
||||
["Magram Clan Centaur"] = "Magram Clan Centaur",
|
||||
Netherwing = "Netherwing",
|
||||
Neutral = "Neutral",
|
||||
["Ogri'la"] = "Ogri'la",
|
||||
Orgrimmar = "Orgrimmar",
|
||||
Ratchet = "Ratchet",
|
||||
Ravenholdt = "Ravenholdt",
|
||||
Revered = "Revered",
|
||||
["Sha'tari Skyguard"] = "Sha'tari Skyguard",
|
||||
["Shattered Sun Offensive"] = "Shattered Sun Offensive",
|
||||
["Shen'dralar"] = "Shen'dralar",
|
||||
["Silvermoon City"] = "Silvermoon City",
|
||||
["Silverwing Sentinels"] = "Silverwing Sentinels",
|
||||
Sporeggar = "Sporeggar",
|
||||
["Stormpike Guard"] = "Stormpike Guard",
|
||||
Stormwind = "Stormwind",
|
||||
Syndicate = "Syndicate",
|
||||
["The Aldor"] = "The Aldor",
|
||||
["The Ashen Verdict"] = "The Ashen Verdict",
|
||||
["The Consortium"] = "The Consortium",
|
||||
["The Defilers"] = "The Defilers",
|
||||
["The Frostborn"] = "The Frostborn",
|
||||
["The Hand of Vengeance"] = "The Hand of Vengeance",
|
||||
["The Kalu'ak"] = "The Kalu'ak",
|
||||
["The League of Arathor"] = "The League of Arathor",
|
||||
["The Mag'har"] = "The Mag'har",
|
||||
["The Oracles"] = "The Oracles",
|
||||
["The Scale of the Sands"] = "The Scale of the Sands",
|
||||
["The Scryers"] = "The Scryers",
|
||||
["The Sha'tar"] = "The Sha'tar",
|
||||
["The Silver Covenant"] = "The Silver Covenant",
|
||||
["The Sons of Hodir"] = "The Sons of Hodir",
|
||||
["The Sunreavers"] = "The Sunreavers",
|
||||
["The Taunka"] = "The Taunka",
|
||||
["The Violet Eye"] = "The Violet Eye",
|
||||
["The Wyrmrest Accord"] = "The Wyrmrest Accord",
|
||||
["Thorium Brotherhood"] = "Thorium Brotherhood",
|
||||
Thrallmar = "Thrallmar",
|
||||
["Thunder Bluff"] = "Thunder Bluff",
|
||||
["Timbermaw Hold"] = "Timbermaw Hold",
|
||||
Tranquillien = "Tranquillien",
|
||||
Undercity = "Undercity",
|
||||
["Valiance Expedition"] = "Valiance Expedition",
|
||||
["Warsong Offensive"] = "Warsong Offensive",
|
||||
["Warsong Outriders"] = "Warsong Outriders",
|
||||
["Wildhammer Clan"] = "Wildhammer Clan",
|
||||
["Winterfin Retreat"] = "Winterfin Retreat",
|
||||
["Wintersaber Trainers"] = "Wintersaber Trainers",
|
||||
["Zandalar Tribe"] = "Zandalar Tribe",
|
||||
}
|
||||
|
||||
|
||||
if GAME_LOCALE == "enUS" then
|
||||
lib:SetCurrentTranslations(true)
|
||||
elseif GAME_LOCALE == "deDE" then
|
||||
lib:SetCurrentTranslations {
|
||||
Alliance = "Allianz",
|
||||
["Alliance Vanguard"] = "Vorposten der Allianz",
|
||||
["Argent Crusade"] = "Argentumkreuzzug",
|
||||
["Argent Dawn"] = "Argentumdämmerung",
|
||||
["Ashtongue Deathsworn"] = "Die Todeshörigen",
|
||||
["Bloodsail Buccaneers"] = "Blutsegelbukaniere",
|
||||
["Booty Bay"] = "Beutebucht",
|
||||
["Brood of Nozdormu"] = "Brut Nozdormus",
|
||||
["Cenarion Circle"] = "Zirkel des Cenarius",
|
||||
["Cenarion Expedition"] = "Expedition des Cenarius",
|
||||
["Darkmoon Faire"] = "Dunkelmond-Jahrmarkt",
|
||||
["Darkspear Trolls"] = "Dunkelspeertrolle",
|
||||
Darnassus = "Darnassus",
|
||||
Everlook = "Ewige Warte",
|
||||
Exalted = "Ehrfürchtig",
|
||||
Exodar = "Die Exodar",
|
||||
["Explorers' League"] = "Forscherliga",
|
||||
["Frenzyheart Tribe"] = "Stamm der Wildherzen",
|
||||
Friendly = "Freundlich",
|
||||
["Frostwolf Clan"] = "Frostwolfklan",
|
||||
Gadgetzan = "Gadgetzan",
|
||||
["Gelkis Clan Centaur"] = "Gelkisklan",
|
||||
["Gnomeregan Exiles"] = "Gnomeregangnome",
|
||||
Honored = "Wohlwollend",
|
||||
["Honor Hold"] = "Ehrenfeste",
|
||||
Horde = "Horde",
|
||||
["Horde Expedition"] = "Expedition der Horde",
|
||||
["Hydraxian Waterlords"] = "Hydraxianer",
|
||||
Ironforge = "Eisenschmiede",
|
||||
["Keepers of Time"] = "Hüter der Zeit",
|
||||
["Kirin Tor"] = "Kirin Tor",
|
||||
["Knights of the Ebon Blade"] = "Ritter der Schwarzen Klinge",
|
||||
Kurenai = "Kurenai",
|
||||
["Lower City"] = "Unteres Viertel",
|
||||
["Magram Clan Centaur"] = "Magramklan",
|
||||
Netherwing = "Netherschwingen",
|
||||
Neutral = "Neutral",
|
||||
["Ogri'la"] = "Ogri'la",
|
||||
Orgrimmar = "Orgrimmar",
|
||||
Ratchet = "Ratschet",
|
||||
Ravenholdt = "Rabenholdt",
|
||||
Revered = "Respektvoll",
|
||||
["Sha'tari Skyguard"] = "Himmelswache der Sha'tari",
|
||||
["Shattered Sun Offensive"] = "Offensive der Zerschmetterten Sonne",
|
||||
["Shen'dralar"] = "Shen'dralar",
|
||||
["Silvermoon City"] = "Silbermond",
|
||||
["Silverwing Sentinels"] = "Silberschwingen",
|
||||
Sporeggar = "Sporeggar",
|
||||
["Stormpike Guard"] = "Sturmlanzengarde",
|
||||
Stormwind = "Sturmwind",
|
||||
Syndicate = "Syndikat",
|
||||
["The Aldor"] = "Die Aldor",
|
||||
["The Ashen Verdict"] = "Das Äscherne Verdikt",
|
||||
["The Consortium"] = "Das Konsortium",
|
||||
["The Defilers"] = "Die Entweihten",
|
||||
["The Frostborn"] = "Die Frosterben",
|
||||
["The Hand of Vengeance"] = "Die Hand der Rache",
|
||||
["The Kalu'ak"] = "Die Kalu'ak",
|
||||
["The League of Arathor"] = "Der Bund von Arathor",
|
||||
["The Mag'har"] = "Die Mag'har",
|
||||
["The Oracles"] = "Die Orakel",
|
||||
["The Scale of the Sands"] = "Die Wächter der Sande",
|
||||
["The Scryers"] = "Die Seher",
|
||||
["The Sha'tar"] = "Die Sha'tar",
|
||||
["The Silver Covenant"] = "Der Silberbund",
|
||||
["The Sons of Hodir"] = "Die Söhne Hodirs",
|
||||
["The Sunreavers"] = "Die Sonnenhäscher",
|
||||
["The Taunka"] = "Die Taunka",
|
||||
["The Violet Eye"] = "Das Violette Auge",
|
||||
["The Wyrmrest Accord"] = "Der Wyrmruhpakt",
|
||||
["Thorium Brotherhood"] = "Thoriumbruderschaft",
|
||||
Thrallmar = "Thrallmar",
|
||||
["Thunder Bluff"] = "Donnerfels",
|
||||
["Timbermaw Hold"] = "Holzschlundfeste",
|
||||
Tranquillien = "Tristessa",
|
||||
Undercity = "Unterstadt",
|
||||
["Valiance Expedition"] = "Expedition Valianz",
|
||||
["Warsong Offensive"] = "Kriegshymnenoffensive",
|
||||
["Warsong Outriders"] = "Vorhut des Kriegshymnenklan",
|
||||
["Wildhammer Clan"] = "Wildhammerklan",
|
||||
["Winterfin Retreat"] = "Zuflucht der Winterflossen",
|
||||
["Wintersaber Trainers"] = "Wintersäblerausbilder",
|
||||
["Zandalar Tribe"] = "Stamm der Zandalari",
|
||||
}
|
||||
elseif GAME_LOCALE == "frFR" then
|
||||
lib:SetCurrentTranslations {
|
||||
Alliance = "Alliance",
|
||||
["Alliance Vanguard"] = "Avant-garde de l'Alliance",
|
||||
["Argent Crusade"] = "La Croisade d'argent",
|
||||
["Argent Dawn"] = "Aube d'argent",
|
||||
["Ashtongue Deathsworn"] = "Ligemort cendrelangue",
|
||||
["Bloodsail Buccaneers"] = "La Voile sanglante",
|
||||
["Booty Bay"] = "Baie-du-Butin",
|
||||
["Brood of Nozdormu"] = "Progéniture de Nozdormu",
|
||||
["Cenarion Circle"] = "Cercle cénarien",
|
||||
["Cenarion Expedition"] = "Expédition cénarienne",
|
||||
["Darkmoon Faire"] = "Foire de Sombrelune",
|
||||
["Darkspear Trolls"] = "Trolls Sombrelance",
|
||||
Darnassus = "Darnassus",
|
||||
Everlook = "Long-guet",
|
||||
Exalted = "Exalté",
|
||||
Exodar = "Exodar",
|
||||
["Explorers' League"] = "Ligue des explorateurs",
|
||||
["Frenzyheart Tribe"] = "La tribu Frénécœur",
|
||||
Friendly = "Amical",
|
||||
["Frostwolf Clan"] = "Clan Loup-de-givre",
|
||||
Gadgetzan = "Gadgetzan",
|
||||
["Gelkis Clan Centaur"] = "Centaures (Gelkis)",
|
||||
["Gnomeregan Exiles"] = "Exilés de Gnomeregan",
|
||||
Honored = "Honoré",
|
||||
["Honor Hold"] = "Bastion de l'Honneur",
|
||||
Horde = "Horde",
|
||||
["Horde Expedition"] = "Expédition de la Horde",
|
||||
["Hydraxian Waterlords"] = "Les Hydraxiens",
|
||||
Ironforge = "Forgefer",
|
||||
["Keepers of Time"] = "Gardiens du Temps",
|
||||
["Kirin Tor"] = "Kirin Tor",
|
||||
["Knights of the Ebon Blade"] = "Chevaliers de la Lame d'ébène",
|
||||
Kurenai = "Kurenaï",
|
||||
["Lower City"] = "Ville basse",
|
||||
["Magram Clan Centaur"] = "Centaures (Magram)",
|
||||
Netherwing = "Aile-du-Néant",
|
||||
Neutral = "Neutre",
|
||||
["Ogri'la"] = "Ogri'la",
|
||||
Orgrimmar = "Orgrimmar",
|
||||
Ratchet = "Cabestan",
|
||||
Ravenholdt = "Ravenholdt",
|
||||
Revered = "Révéré",
|
||||
["Sha'tari Skyguard"] = "Garde-ciel sha'tari",
|
||||
["Shattered Sun Offensive"] = "Opération Soleil brisé",
|
||||
["Shen'dralar"] = "Shen'dralar",
|
||||
["Silvermoon City"] = "Lune-d'argent",
|
||||
["Silverwing Sentinels"] = "Sentinelles d'Aile-argent",
|
||||
Sporeggar = "Sporeggar",
|
||||
["Stormpike Guard"] = "Garde Foudrepique",
|
||||
Stormwind = "Hurlevent",
|
||||
Syndicate = "Syndicat",
|
||||
["The Aldor"] = "L'Aldor",
|
||||
["The Ashen Verdict"] = "Le Verdict des cendres",
|
||||
["The Consortium"] = "Le Consortium",
|
||||
["The Defilers"] = "Les Profanateurs",
|
||||
["The Frostborn"] = "Les Givre-nés",
|
||||
["The Hand of Vengeance"] = "La Main de la vengeance",
|
||||
["The Kalu'ak"] = "Les Kalu'aks",
|
||||
["The League of Arathor"] = "La Ligue d'Arathor",
|
||||
["The Mag'har"] = "Les Mag'har",
|
||||
["The Oracles"] = "Les Oracles",
|
||||
["The Scale of the Sands"] = "La Balance des sables",
|
||||
["The Scryers"] = "Les Clairvoyants",
|
||||
["The Sha'tar"] = "Les Sha'tar",
|
||||
["The Silver Covenant"] = "Le Concordat argenté",
|
||||
["The Sons of Hodir"] = "Les Fils de Hodir",
|
||||
["The Sunreavers"] = "Les Saccage-soleil",
|
||||
["The Taunka"] = "Les Taunkas",
|
||||
["The Violet Eye"] = "L'Œil pourpre",
|
||||
["The Wyrmrest Accord"] = "L'Accord du Repos du ver",
|
||||
["Thorium Brotherhood"] = "Confrérie du thorium",
|
||||
Thrallmar = "Thrallmar",
|
||||
["Thunder Bluff"] = "Les Pitons du Tonnerre",
|
||||
["Timbermaw Hold"] = "Les Grumegueules",
|
||||
Tranquillien = "Tranquillien",
|
||||
Undercity = "Fossoyeuse",
|
||||
["Valiance Expedition"] = "Expédition de la Bravoure",
|
||||
["Warsong Offensive"] = "Offensive chanteguerre",
|
||||
["Warsong Outriders"] = "Voltigeurs Chanteguerre",
|
||||
["Wildhammer Clan"] = "Clan Marteau-hardi",
|
||||
["Winterfin Retreat"] = "Retraite des Ailerons-d'hiver",
|
||||
["Wintersaber Trainers"] = "Éleveurs de sabres-d'hiver",
|
||||
["Zandalar Tribe"] = "Tribu Zandalar",
|
||||
}
|
||||
elseif GAME_LOCALE == "koKR" then
|
||||
lib:SetCurrentTranslations {
|
||||
Alliance = "얼라이언스",
|
||||
["Alliance Vanguard"] = "얼라이언스 선봉대",
|
||||
["Argent Crusade"] = "은빛십자군",
|
||||
["Argent Dawn"] = "은빛 여명회",
|
||||
["Ashtongue Deathsworn"] = "잿빛혓바닥 결사단",
|
||||
["Bloodsail Buccaneers"] = "붉은 해적단",
|
||||
["Booty Bay"] = "무법항",
|
||||
["Brood of Nozdormu"] = "노즈도르무 혈족",
|
||||
["Cenarion Circle"] = "세나리온 의회",
|
||||
["Cenarion Expedition"] = "세나리온 원정대",
|
||||
["Darkmoon Faire"] = "다크문 유랑단",
|
||||
["Darkspear Trolls"] = "검은창 트롤",
|
||||
Darnassus = "다르나서스",
|
||||
Everlook = "눈망루 마을",
|
||||
Exalted = "확고한 동맹",
|
||||
Exodar = "엑소다르",
|
||||
["Explorers' League"] = "탐험가 연맹",
|
||||
["Frenzyheart Tribe"] = "광란의심장일족",
|
||||
Friendly = "약간 우호적",
|
||||
["Frostwolf Clan"] = "서리늑대 부족",
|
||||
Gadgetzan = "가젯잔",
|
||||
["Gelkis Clan Centaur"] = "겔키스 부족 켄타로우스",
|
||||
["Gnomeregan Exiles"] = "놈리건",
|
||||
Honored = "우호적",
|
||||
["Honor Hold"] = "명예의 요새",
|
||||
Horde = "호드",
|
||||
["Horde Expedition"] = "호드 원정대",
|
||||
["Hydraxian Waterlords"] = "히드락시안 물의 군주",
|
||||
Ironforge = "아이언포지",
|
||||
["Keepers of Time"] = "시간의 수호자",
|
||||
["Kirin Tor"] = "키린 토",
|
||||
["Knights of the Ebon Blade"] = "칠흑의 기사단",
|
||||
Kurenai = "쿠레나이",
|
||||
["Lower City"] = "고난의 거리",
|
||||
["Magram Clan Centaur"] = "마그람 부족 켄타로우스",
|
||||
Netherwing = "황천의 용군단",
|
||||
Neutral = "중립적",
|
||||
["Ogri'la"] = "오그릴라",
|
||||
Orgrimmar = "오그리마",
|
||||
Ratchet = "톱니항",
|
||||
Ravenholdt = "라벤홀트",
|
||||
Revered = "매우 우호적",
|
||||
["Sha'tari Skyguard"] = "샤타리 하늘경비대",
|
||||
["Shattered Sun Offensive"] = "무너진 태양 공격대",
|
||||
["Shen'dralar"] = "센드렐라",
|
||||
["Silvermoon City"] = "실버문",
|
||||
["Silverwing Sentinels"] = "은빛날개 파수대",
|
||||
Sporeggar = "스포어가르",
|
||||
["Stormpike Guard"] = "스톰파이크 경비대",
|
||||
Stormwind = "스톰윈드",
|
||||
Syndicate = "비밀결사대",
|
||||
["The Aldor"] = "알도르 사제회",
|
||||
["The Ashen Verdict"] = "잿빛 선고단",
|
||||
["The Consortium"] = "무역연합",
|
||||
["The Defilers"] = "포세이큰 파멸단",
|
||||
["The Frostborn"] = "서릿결부족 드워프",
|
||||
["The Hand of Vengeance"] = "복수의 대리인",
|
||||
["The Kalu'ak"] = "칼루아크",
|
||||
["The League of Arathor"] = "아라소르 연맹",
|
||||
["The Mag'har"] = "마그하르",
|
||||
["The Oracles"] = "점쟁이 조합",
|
||||
["The Scale of the Sands"] = "시간의 중재자",
|
||||
["The Scryers"] = "점술가 길드",
|
||||
["The Sha'tar"] = "샤타르",
|
||||
["The Silver Covenant"] = "은빛 서약단",
|
||||
["The Sons of Hodir"] = "호디르의 후예",
|
||||
["The Sunreavers"] = "선리버",
|
||||
["The Taunka"] = "타운카",
|
||||
["The Violet Eye"] = "보랏빛 눈의 감시자",
|
||||
["The Wyrmrest Accord"] = "고룡쉼터 사원 용군단",
|
||||
["Thorium Brotherhood"] = "토륨 대장조합 ",
|
||||
Thrallmar = "스랄마",
|
||||
["Thunder Bluff"] = "썬더 블러프",
|
||||
["Timbermaw Hold"] = "나무구렁 요새",
|
||||
Tranquillien = "트랜퀼리엔",
|
||||
Undercity = "언더시티",
|
||||
["Valiance Expedition"] = "용맹의 원정대",
|
||||
["Warsong Offensive"] = "전쟁노래 공격대",
|
||||
["Warsong Outriders"] = "전쟁노래 정찰대",
|
||||
["Wildhammer Clan"] = "와일드해머 부족",
|
||||
["Winterfin Retreat"] = "겨울지느러미 은신처",
|
||||
["Wintersaber Trainers"] = "눈호랑이 조련사",
|
||||
["Zandalar Tribe"] = "잔달라 부족",
|
||||
}
|
||||
elseif GAME_LOCALE == "esES" then
|
||||
lib:SetCurrentTranslations {
|
||||
Alliance = "Alianza",
|
||||
["Alliance Vanguard"] = "Vanguardia de la Alianza",
|
||||
["Argent Crusade"] = "Cruzada Argenta",
|
||||
["Argent Dawn"] = "El Alba Argenta",
|
||||
["Ashtongue Deathsworn"] = "Juramorte Lengua de ceniza",
|
||||
["Bloodsail Buccaneers"] = "Bucaneros Velasangre",
|
||||
["Booty Bay"] = "Bahía del Botín",
|
||||
["Brood of Nozdormu"] = "Linaje de Nozdormu",
|
||||
["Cenarion Circle"] = "Círculo Cenarion",
|
||||
["Cenarion Expedition"] = "Expedición Cenarion",
|
||||
["Darkmoon Faire"] = "Feria de la Luna Negra",
|
||||
["Darkspear Trolls"] = "Trols Lanza Negra",
|
||||
Darnassus = "Darnassus",
|
||||
Everlook = "Vista Eterna",
|
||||
Exalted = "Exaltado",
|
||||
Exodar = "El Exodar",
|
||||
["Explorers' League"] = "Liga de Expedicionarios",
|
||||
["Frenzyheart Tribe"] = "Tribu Corazón Frenético",
|
||||
Friendly = "Amistoso",
|
||||
["Frostwolf Clan"] = "Clan Lobo Gélido",
|
||||
Gadgetzan = "Gadgetzan",
|
||||
["Gelkis Clan Centaur"] = "Centauros del clan Gelkis",
|
||||
["Gnomeregan Exiles"] = "Exiliados de Gnomeregan",
|
||||
Honored = "Honorable",
|
||||
["Honor Hold"] = "Bastión del Honor",
|
||||
Horde = "Horda",
|
||||
["Horde Expedition"] = "Expedición de la Horda",
|
||||
["Hydraxian Waterlords"] = "Srs. del Agua de Hydraxis",
|
||||
Ironforge = "Forjaz",
|
||||
["Keepers of Time"] = "Vigilantes del Tiempo",
|
||||
["Kirin Tor"] = "Kirin Tor",
|
||||
["Knights of the Ebon Blade"] = "Caballeros de la Espada de Ébano",
|
||||
Kurenai = "Kurenai",
|
||||
["Lower City"] = "Bajo Arrabal",
|
||||
["Magram Clan Centaur"] = "Centauros del clan Magram",
|
||||
Netherwing = "Ala Abisal",
|
||||
Neutral = "Neutral",
|
||||
["Ogri'la"] = "Ogri'la",
|
||||
Orgrimmar = "Orgrimmar",
|
||||
Ratchet = "Trinquete",
|
||||
Ravenholdt = "Ravenholdt",
|
||||
Revered = "Reverenciado",
|
||||
["Sha'tari Skyguard"] = "Guardia del cielo Sha'tari",
|
||||
["Shattered Sun Offensive"] = "Ofensiva Sol Devastado",
|
||||
["Shen'dralar"] = "Shen'dralar",
|
||||
["Silvermoon City"] = "Ciudad de Lunargenta",
|
||||
["Silverwing Sentinels"] = "Centinelas Ala de Plata",
|
||||
Sporeggar = "Esporaggar",
|
||||
["Stormpike Guard"] = "Guardia Pico Tormenta",
|
||||
Stormwind = "Ventormenta",
|
||||
Syndicate = "La Hermandad",
|
||||
["The Aldor"] = "Los Aldor",
|
||||
["The Ashen Verdict"] = "El Veredicto Cinéreo",
|
||||
["The Consortium"] = "El Consorcio",
|
||||
["The Defilers"] = "Los Rapiñadores",
|
||||
["The Frostborn"] = "Los Natoescarcha",
|
||||
["The Hand of Vengeance"] = "La Mano de la Venganza",
|
||||
["The Kalu'ak"] = "Los Kalu'ak",
|
||||
["The League of Arathor"] = "Liga de Arathor",
|
||||
["The Mag'har"] = "Los Mag'har",
|
||||
["The Oracles"] = "Los Oráculos",
|
||||
["The Scale of the Sands"] = "La Escama de las Arenas",
|
||||
["The Scryers"] = "Los Arúspices",
|
||||
["The Sha'tar"] = "Los Sha'tar",
|
||||
["The Silver Covenant"] = "El Pacto de Plata",
|
||||
["The Sons of Hodir"] = "Los Hijos de Hodir",
|
||||
["The Sunreavers"] = "Los Atracasol",
|
||||
["The Taunka"] = "Los Taunka",
|
||||
["The Violet Eye"] = "El Ojo Violeta",
|
||||
["The Wyrmrest Accord"] = "El Acuerdo del Reposo del Dragón",
|
||||
["Thorium Brotherhood"] = "Hermandad del Torio",
|
||||
Thrallmar = "Thrallmar",
|
||||
["Thunder Bluff"] = "Cima del Trueno",
|
||||
["Timbermaw Hold"] = "Bastión Fauces de Madera",
|
||||
Tranquillien = "Tranquillien",
|
||||
Undercity = "Entrañas",
|
||||
["Valiance Expedition"] = "Expedición de Denuedo",
|
||||
["Warsong Offensive"] = "Ofensiva Grito de Guerra",
|
||||
["Warsong Outriders"] = "Escoltas Grito de Guerra",
|
||||
["Wildhammer Clan"] = "Clan Martillo Salvaje",
|
||||
["Winterfin Retreat"] = "Retiro Aleta Invernal",
|
||||
["Wintersaber Trainers"] = "Instructores de Sableinvernales",
|
||||
["Zandalar Tribe"] = "Tribu Zandalar",
|
||||
}
|
||||
elseif GAME_LOCALE == "esMX" then
|
||||
lib:SetCurrentTranslations {
|
||||
Alliance = "Alianza",
|
||||
["Alliance Vanguard"] = "Vanguardia de la Alianza",
|
||||
["Argent Crusade"] = "Cruzada Argenta",
|
||||
["Argent Dawn"] = "Alba Argenta",
|
||||
["Ashtongue Deathsworn"] = "Juramorte Lengua de ceniza",
|
||||
["Bloodsail Buccaneers"] = "Bucaneros Velasangre",
|
||||
["Booty Bay"] = "Bahía del Botín",
|
||||
["Brood of Nozdormu"] = "Linaje de Nozdormu",
|
||||
["Cenarion Circle"] = "Círculo Cenarion",
|
||||
["Cenarion Expedition"] = "Expedición Cenarion",
|
||||
["Darkmoon Faire"] = "Feria de la Luna Negra",
|
||||
["Darkspear Trolls"] = "Trols Lanza Negra",
|
||||
Darnassus = "Darnassus",
|
||||
Everlook = "Vista Eterna",
|
||||
Exalted = "Exaltado",
|
||||
Exodar = "Exodar",
|
||||
["Explorers' League"] = "Liga de Expedicionarios",
|
||||
["Frenzyheart Tribe"] = "Tribu Corazón Frenético",
|
||||
Friendly = "Amistoso",
|
||||
["Frostwolf Clan"] = "Clan Lobo Gélido",
|
||||
Gadgetzan = "Gadgetzan",
|
||||
["Gelkis Clan Centaur"] = "Centauro del clan Gelkis",
|
||||
["Gnomeregan Exiles"] = "Exiliados de Gnomeregan",
|
||||
Honored = "Honorable",
|
||||
["Honor Hold"] = "Bastión del Honor",
|
||||
Horde = "Horda",
|
||||
["Horde Expedition"] = "Expedición de la Horda",
|
||||
["Hydraxian Waterlords"] = "Srs. del Agua de Hydraxis",
|
||||
Ironforge = "Forjaz",
|
||||
["Keepers of Time"] = "Vigilantes del tiempo",
|
||||
["Kirin Tor"] = "Kirin Tor",
|
||||
["Knights of the Ebon Blade"] = "Caballeros de la Espada de Ébano",
|
||||
Kurenai = "Kurenai",
|
||||
["Lower City"] = "Bajo Arrabal",
|
||||
["Magram Clan Centaur"] = "Centauro del clan Magram",
|
||||
Netherwing = "Ala Abisal",
|
||||
Neutral = "Neutral",
|
||||
["Ogri'la"] = "Ogri'la",
|
||||
Orgrimmar = "Orgrimmar",
|
||||
Ratchet = "Trinquete",
|
||||
Ravenholdt = "Ravenholdt",
|
||||
Revered = "Reverenciado",
|
||||
["Sha'tari Skyguard"] = "Guardia del cielo Sha'tari",
|
||||
["Shattered Sun Offensive"] = "Ofensiva Sol Devastado",
|
||||
["Shen'dralar"] = "Shen'dralar",
|
||||
["Silvermoon City"] = "Ciudad de Lunargenta",
|
||||
["Silverwing Sentinels"] = "Centinelas Ala de Plata",
|
||||
Sporeggar = "Esporaggar",
|
||||
["Stormpike Guard"] = "Guardia Pico Tormenta",
|
||||
Stormwind = "Ventormenta",
|
||||
Syndicate = "La Hermandad",
|
||||
["The Aldor"] = "Los Aldor",
|
||||
-- ["The Ashen Verdict"] = "",
|
||||
["The Consortium"] = "El Consorcio",
|
||||
["The Defilers"] = "Los Rapiñadores",
|
||||
["The Frostborn"] = "Los Natoescarcha",
|
||||
["The Hand of Vengeance"] = "La Mano de la Venganza",
|
||||
["The Kalu'ak"] = "Los Kalu'ak",
|
||||
["The League of Arathor"] = "Liga de Arathor",
|
||||
["The Mag'har"] = "Los Mag'har",
|
||||
["The Oracles"] = "Los Oráculos",
|
||||
["The Scale of the Sands"] = "La Escama de las Arenas",
|
||||
["The Scryers"] = "Los Arúspices",
|
||||
["The Sha'tar"] = "Los Sha'tar",
|
||||
["The Silver Covenant"] = "El Pacto de Plata",
|
||||
["The Sons of Hodir"] = "Los Hijos de Hodir",
|
||||
["The Sunreavers"] = "Los Atracasol",
|
||||
["The Taunka"] = "Los taunka",
|
||||
["The Violet Eye"] = "El Ojo Violeta",
|
||||
["The Wyrmrest Accord"] = "El Acuerdo del Reposo del Dragón",
|
||||
["Thorium Brotherhood"] = "Hermandad del torio",
|
||||
Thrallmar = "Thrallmar",
|
||||
["Thunder Bluff"] = "Cima del Trueno",
|
||||
["Timbermaw Hold"] = "Bastión Fauces de Madera",
|
||||
Tranquillien = "Tranquilien",
|
||||
Undercity = "Entrañas",
|
||||
["Valiance Expedition"] = "Expedición de Denuedo",
|
||||
["Warsong Offensive"] = "Ofensiva Grito de Guerra",
|
||||
["Warsong Outriders"] = "Escoltas Grito de Guerra",
|
||||
["Wildhammer Clan"] = "Clan Martillo Salvaje",
|
||||
["Winterfin Retreat"] = "Retiro Aleta Invernal",
|
||||
["Wintersaber Trainers"] = "Instructores de Sableinvernales",
|
||||
["Zandalar Tribe"] = "Tribu Zandalar",
|
||||
}
|
||||
elseif GAME_LOCALE == "ruRU" then
|
||||
lib:SetCurrentTranslations {
|
||||
Alliance = "Альянс",
|
||||
["Alliance Vanguard"] = "Авангард Альянса",
|
||||
["Argent Crusade"] = "Серебряный Авангард",
|
||||
["Argent Dawn"] = "Серебряный Рассвет",
|
||||
["Ashtongue Deathsworn"] = "Пеплоусты-служители",
|
||||
["Bloodsail Buccaneers"] = "Пираты Кровавого Паруса",
|
||||
["Booty Bay"] = "Пиратская бухта",
|
||||
["Brood of Nozdormu"] = "Род Ноздорму",
|
||||
["Cenarion Circle"] = "Круг Кенария",
|
||||
["Cenarion Expedition"] = "Экспедиция Ценариона",
|
||||
["Darkmoon Faire"] = "Ярмарка Новолуния",
|
||||
["Darkspear Trolls"] = "Тролли Черного Копья",
|
||||
Darnassus = "Дарнас",
|
||||
Everlook = "Круговзор",
|
||||
Exalted = "Превознесение",
|
||||
Exodar = "Экзодар",
|
||||
["Explorers' League"] = "Лига исследователей",
|
||||
["Frenzyheart Tribe"] = "Племя Мятежного Сердца",
|
||||
Friendly = "Дружелюбие",
|
||||
["Frostwolf Clan"] = "Клан Северного Волка",
|
||||
Gadgetzan = "Прибамбасск",
|
||||
["Gelkis Clan Centaur"] = "Кентавры из племени Гелкис",
|
||||
["Gnomeregan Exiles"] = "Изгнанники Гномрегана",
|
||||
Honored = "Уважение",
|
||||
["Honor Hold"] = "Оплот Чести",
|
||||
Horde = "Орда",
|
||||
["Horde Expedition"] = "Экспедиция Орды",
|
||||
["Hydraxian Waterlords"] = "Гидраксианские Повелители Вод",
|
||||
Ironforge = "Стальгорн",
|
||||
["Keepers of Time"] = "Хранители Времени",
|
||||
["Kirin Tor"] = "Кирин-Тор",
|
||||
["Knights of the Ebon Blade"] = "Рыцари Черного Клинка",
|
||||
Kurenai = "Куренай",
|
||||
["Lower City"] = "Нижний Город",
|
||||
["Magram Clan Centaur"] = "Кентавры из племени Маграм",
|
||||
Netherwing = "Крылья Пустоты",
|
||||
Neutral = "Равнодушие",
|
||||
["Ogri'la"] = "Огри'ла",
|
||||
Orgrimmar = "Оргриммар",
|
||||
Ratchet = "Кабестан",
|
||||
Ravenholdt = "Черный Ворон",
|
||||
Revered = "Почтение",
|
||||
["Sha'tari Skyguard"] = "Стражи Небес Ша'тар",
|
||||
["Shattered Sun Offensive"] = "Армия Расколотого Солнца",
|
||||
["Shen'dralar"] = "Шен'дралар",
|
||||
["Silvermoon City"] = "Луносвет",
|
||||
["Silverwing Sentinels"] = "Среброкрылые Часовые",
|
||||
Sporeggar = "Спореггар",
|
||||
["Stormpike Guard"] = "Стража Грозовой Вершины",
|
||||
Stormwind = "Штормград",
|
||||
Syndicate = "Синдикат",
|
||||
["The Aldor"] = "Алдоры",
|
||||
["The Ashen Verdict"] = "Пепельный союз",
|
||||
["The Consortium"] = "Консорциум",
|
||||
["The Defilers"] = "Осквернители",
|
||||
["The Frostborn"] = "Зиморожденные",
|
||||
["The Hand of Vengeance"] = "Карающая длань",
|
||||
["The Kalu'ak"] = "Калу'ак",
|
||||
["The League of Arathor"] = "Лига Аратора",
|
||||
["The Mag'har"] = "Маг'хары",
|
||||
["The Oracles"] = "Оракулы",
|
||||
["The Scale of the Sands"] = "Песчаная Чешуя",
|
||||
["The Scryers"] = "Провидцы",
|
||||
["The Sha'tar"] = "Ша'тар",
|
||||
["The Silver Covenant"] = "Серебряный Союз",
|
||||
["The Sons of Hodir"] = "Сыновья Ходира",
|
||||
["The Sunreavers"] = "Похитители солнца",
|
||||
["The Taunka"] = "Таунка",
|
||||
["The Violet Eye"] = "Аметистовое Око",
|
||||
["The Wyrmrest Accord"] = "Драконий союз",
|
||||
["Thorium Brotherhood"] = "Братство Тория",
|
||||
Thrallmar = "Траллмар",
|
||||
["Thunder Bluff"] = "Громовой Утес",
|
||||
["Timbermaw Hold"] = "Древобрюхи",
|
||||
Tranquillien = "Транквиллион",
|
||||
Undercity = "Подгород",
|
||||
["Valiance Expedition"] = "Экспедиция Отважных",
|
||||
["Warsong Offensive"] = "Армия Песни Войны",
|
||||
["Warsong Outriders"] = "Всадники Песни Войны",
|
||||
["Wildhammer Clan"] = "Неистовый Молот",
|
||||
["Winterfin Retreat"] = "Холодный Плавник",
|
||||
["Wintersaber Trainers"] = "Укротители ледопардов",
|
||||
["Zandalar Tribe"] = "Племя Зандалар",
|
||||
}
|
||||
elseif GAME_LOCALE == "zhCN" then
|
||||
lib:SetCurrentTranslations {
|
||||
Alliance = "联盟",
|
||||
["Alliance Vanguard"] = "联盟先遣军",
|
||||
["Argent Crusade"] = "银色北伐军",
|
||||
["Argent Dawn"] = "银色黎明",
|
||||
["Ashtongue Deathsworn"] = "灰舌死誓者",
|
||||
["Bloodsail Buccaneers"] = "血帆海盗",
|
||||
["Booty Bay"] = "藏宝海湾",
|
||||
["Brood of Nozdormu"] = "诺兹多姆的子嗣",
|
||||
["Cenarion Circle"] = "塞纳里奥议会",
|
||||
["Cenarion Expedition"] = "塞纳里奥远征队",
|
||||
["Darkmoon Faire"] = "暗月马戏团",
|
||||
["Darkspear Trolls"] = "暗矛巨魔",
|
||||
Darnassus = "达纳苏斯",
|
||||
Everlook = "永望镇",
|
||||
Exalted = "崇拜",
|
||||
Exodar = "埃索达",
|
||||
["Explorers' League"] = "探险者协会",
|
||||
["Frenzyheart Tribe"] = "狂心氏族",
|
||||
Friendly = "友善",
|
||||
["Frostwolf Clan"] = "霜狼氏族",
|
||||
Gadgetzan = "加基森",
|
||||
["Gelkis Clan Centaur"] = "吉尔吉斯半人马",
|
||||
["Gnomeregan Exiles"] = "诺莫瑞根流亡者",
|
||||
Honored = "尊敬",
|
||||
["Honor Hold"] = "荣耀堡",
|
||||
Horde = "部落",
|
||||
["Horde Expedition"] = "部落先遣军",
|
||||
["Hydraxian Waterlords"] = "海达希亚水元素",
|
||||
Ironforge = "铁炉堡",
|
||||
["Keepers of Time"] = "时光守护者",
|
||||
["Kirin Tor"] = "肯瑞托",
|
||||
["Knights of the Ebon Blade"] = "黑锋骑士团",
|
||||
Kurenai = "库雷尼",
|
||||
["Lower City"] = "贫民窟",
|
||||
["Magram Clan Centaur"] = "玛格拉姆半人马",
|
||||
Netherwing = "灵翼之龙",
|
||||
Neutral = "中立",
|
||||
["Ogri'la"] = "奥格瑞拉",
|
||||
Orgrimmar = "奥格瑞玛",
|
||||
Ratchet = "棘齿城",
|
||||
Ravenholdt = "拉文霍德",
|
||||
Revered = "崇敬",
|
||||
["Sha'tari Skyguard"] = "沙塔尔天空卫士",
|
||||
["Shattered Sun Offensive"] = "破碎残阳",
|
||||
["Shen'dralar"] = "辛德拉",
|
||||
["Silvermoon City"] = "银月城",
|
||||
["Silverwing Sentinels"] = "银翼哨兵",
|
||||
Sporeggar = "孢子村",
|
||||
["Stormpike Guard"] = "雷矛卫队",
|
||||
Stormwind = "暴风城",
|
||||
Syndicate = "辛迪加",
|
||||
["The Aldor"] = "奥尔多",
|
||||
["The Ashen Verdict"] = "灰烬审判军",
|
||||
["The Consortium"] = "星界财团",
|
||||
["The Defilers"] = "污染者",
|
||||
["The Frostborn"] = "霜脉矮人",
|
||||
["The Hand of Vengeance"] = "复仇之手",
|
||||
["The Kalu'ak"] = "卡鲁亚克",
|
||||
["The League of Arathor"] = "阿拉索联军",
|
||||
["The Mag'har"] = "玛格汉",
|
||||
["The Oracles"] = "神谕者",
|
||||
["The Scale of the Sands"] = "流沙之鳞",
|
||||
["The Scryers"] = "占星者",
|
||||
["The Sha'tar"] = "沙塔尔",
|
||||
["The Silver Covenant"] = "银色盟约",
|
||||
["The Sons of Hodir"] = "霍迪尔之子",
|
||||
["The Sunreavers"] = "夺日者",
|
||||
["The Taunka"] = "牦牛人",
|
||||
["The Violet Eye"] = "紫罗兰之眼",
|
||||
["The Wyrmrest Accord"] = "龙眠联军",
|
||||
["Thorium Brotherhood"] = "瑟银兄弟会",
|
||||
Thrallmar = "萨尔玛",
|
||||
["Thunder Bluff"] = "雷霆崖",
|
||||
["Timbermaw Hold"] = "木喉要塞",
|
||||
Tranquillien = "塔奎林",
|
||||
Undercity = "幽暗城",
|
||||
["Valiance Expedition"] = "无畏远征军",
|
||||
["Warsong Offensive"] = "战歌远征军",
|
||||
["Warsong Outriders"] = "战歌侦察骑兵",
|
||||
["Wildhammer Clan"] = "蛮锤部族",
|
||||
["Winterfin Retreat"] = "冬鳞避难所",
|
||||
["Wintersaber Trainers"] = "冬刃豹训练师",
|
||||
["Zandalar Tribe"] = "赞达拉部族",
|
||||
}
|
||||
elseif GAME_LOCALE == "zhTW" then
|
||||
lib:SetCurrentTranslations {
|
||||
Alliance = "聯盟",
|
||||
["Alliance Vanguard"] = "聯盟先鋒",
|
||||
["Argent Crusade"] = "銀白十字軍",
|
||||
["Argent Dawn"] = "銀色黎明",
|
||||
["Ashtongue Deathsworn"] = "灰舌死亡誓言者",
|
||||
["Bloodsail Buccaneers"] = "血帆海盜",
|
||||
["Booty Bay"] = "藏寶海灣",
|
||||
["Brood of Nozdormu"] = "諾茲多姆的子嗣",
|
||||
["Cenarion Circle"] = "塞納里奧議會",
|
||||
["Cenarion Expedition"] = "塞納里奧遠征隊",
|
||||
["Darkmoon Faire"] = "暗月馬戲團",
|
||||
["Darkspear Trolls"] = "暗矛食人妖",
|
||||
Darnassus = "達納蘇斯",
|
||||
Everlook = "永望鎮",
|
||||
Exalted = "崇拜",
|
||||
Exodar = "艾克索達",
|
||||
["Explorers' League"] = "探險者協會",
|
||||
["Frenzyheart Tribe"] = "狂心部族",
|
||||
Friendly = "友好",
|
||||
["Frostwolf Clan"] = "霜狼氏族",
|
||||
Gadgetzan = "加基森",
|
||||
["Gelkis Clan Centaur"] = "吉爾吉斯半人馬",
|
||||
["Gnomeregan Exiles"] = "諾姆瑞根流亡者",
|
||||
Honored = "尊敬",
|
||||
["Honor Hold"] = "榮譽堡",
|
||||
Horde = "部落",
|
||||
["Horde Expedition"] = "部落遠征軍",
|
||||
["Hydraxian Waterlords"] = "海達希亞水元素",
|
||||
Ironforge = "鐵爐堡",
|
||||
["Keepers of Time"] = "時光守望者",
|
||||
["Kirin Tor"] = "祈倫托",
|
||||
["Knights of the Ebon Blade"] = "黯刃騎士團",
|
||||
Kurenai = "卡爾奈",
|
||||
["Lower City"] = "陰鬱城",
|
||||
["Magram Clan Centaur"] = "瑪格拉姆半人馬",
|
||||
Netherwing = "虛空之翼",
|
||||
Neutral = "中立",
|
||||
["Ogri'la"] = "歐格利拉",
|
||||
Orgrimmar = "奧格瑪",
|
||||
Ratchet = "棘齒城",
|
||||
Ravenholdt = "拉文霍德",
|
||||
Revered = "崇敬",
|
||||
["Sha'tari Skyguard"] = "薩塔禦天者",
|
||||
["Shattered Sun Offensive"] = "破碎之日進攻部隊",
|
||||
["Shen'dralar"] = "辛德拉",
|
||||
["Silvermoon City"] = "銀月城",
|
||||
["Silverwing Sentinels"] = "銀翼哨兵",
|
||||
Sporeggar = "斯博格爾",
|
||||
["Stormpike Guard"] = "雷矛衛隊",
|
||||
Stormwind = "暴風城",
|
||||
Syndicate = "辛迪加",
|
||||
["The Aldor"] = "奧多爾",
|
||||
["The Ashen Verdict"] = "灰燼裁決軍",
|
||||
["The Consortium"] = "聯合團",
|
||||
["The Defilers"] = "污染者",
|
||||
["The Frostborn"] = "霜誕矮人",
|
||||
["The Hand of Vengeance"] = "復仇之手",
|
||||
["The Kalu'ak"] = "卡魯耶克",
|
||||
["The League of Arathor"] = "阿拉索聯軍",
|
||||
["The Mag'har"] = "瑪格哈",
|
||||
["The Oracles"] = "神諭者",
|
||||
["The Scale of the Sands"] = "流沙之鱗",
|
||||
["The Scryers"] = "占卜者",
|
||||
["The Sha'tar"] = "薩塔",
|
||||
["The Silver Covenant"] = "白銀誓盟",
|
||||
["The Sons of Hodir"] = "霍迪爾之子",
|
||||
["The Sunreavers"] = "奪日者",
|
||||
["The Taunka"] = "坦卡族",
|
||||
["The Violet Eye"] = "紫羅蘭之眼",
|
||||
["The Wyrmrest Accord"] = "龍眠協調者",
|
||||
["Thorium Brotherhood"] = "瑟銀兄弟會",
|
||||
Thrallmar = "索爾瑪",
|
||||
["Thunder Bluff"] = "雷霆崖",
|
||||
["Timbermaw Hold"] = "木喉要塞",
|
||||
Tranquillien = "安寧地",
|
||||
Undercity = "幽暗城",
|
||||
["Valiance Expedition"] = "驍勇遠征隊",
|
||||
["Warsong Offensive"] = "戰歌進攻部隊",
|
||||
["Warsong Outriders"] = "戰歌偵察騎兵",
|
||||
["Wildhammer Clan"] = "蠻錘氏族",
|
||||
["Winterfin Retreat"] = "冬鰭避居地",
|
||||
["Wintersaber Trainers"] = "冬刃豹訓練師",
|
||||
["Zandalar Tribe"] = "贊達拉部族",
|
||||
}
|
||||
|
||||
else
|
||||
error(("%s: Locale %q not supported"):format(MAJOR_VERSION, GAME_LOCALE))
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
## Interface: 30300
|
||||
## LoadOnDemand: 1
|
||||
## Title: Lib: Babble-Faction-3.0
|
||||
## Notes: A library to help with localization of factions.
|
||||
## Notes-zhCN: 为本地化服务的支持库[声望阵营]
|
||||
## Notes-zhTW: 為本地化服務的函式庫[聲望陣營]
|
||||
## Notes-deDE: BabbleLib ist eine Bibliothek, die bei der Lokalisierung helfen soll.
|
||||
## Notes-frFR: Une bibliothèque d'aide à la localisation.
|
||||
## Notes-esES: Una biblioteca para ayudar con las localizaciones.
|
||||
## Notes-ruRU: Библиотека для локализации аддонов.
|
||||
## Author: Daviesh
|
||||
## X-eMail: oma_daviesh@hotmail.com
|
||||
## X-Category: Library
|
||||
## X-License: MIT
|
||||
## X-Curse-Packaged-Version: r112
|
||||
## X-Curse-Project-Name: LibBabble-Faction-3.0
|
||||
## X-Curse-Project-ID: libbabble-faction-3-0
|
||||
## X-Curse-Repository-ID: wow/libbabble-faction-3-0/mainline
|
||||
|
||||
LibStub\LibStub.lua
|
||||
lib.xml
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 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
|
||||
@@ -0,0 +1,5 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="LibBabble-3.0.lua" />
|
||||
<Script file="LibBabble-Faction-3.0.lua" />
|
||||
</Ui>
|
||||
@@ -0,0 +1,292 @@
|
||||
-- LibBabble-3.0 is hereby placed in the Public Domain
|
||||
-- Credits: ckknight
|
||||
local LIBBABBLE_MAJOR, LIBBABBLE_MINOR = "LibBabble-3.0", 2
|
||||
|
||||
local LibBabble = LibStub:NewLibrary(LIBBABBLE_MAJOR, LIBBABBLE_MINOR)
|
||||
if not LibBabble then
|
||||
return
|
||||
end
|
||||
|
||||
local data = LibBabble.data or {}
|
||||
for k,v in pairs(LibBabble) do
|
||||
LibBabble[k] = nil
|
||||
end
|
||||
LibBabble.data = data
|
||||
|
||||
local tablesToDB = {}
|
||||
for namespace, db in pairs(data) do
|
||||
for k,v in pairs(db) do
|
||||
tablesToDB[v] = db
|
||||
end
|
||||
end
|
||||
|
||||
local function warn(message)
|
||||
local _, ret = pcall(error, message, 3)
|
||||
geterrorhandler()(ret)
|
||||
end
|
||||
|
||||
local lookup_mt = { __index = function(self, key)
|
||||
local db = tablesToDB[self]
|
||||
local current_key = db.current[key]
|
||||
if current_key then
|
||||
self[key] = current_key
|
||||
return current_key
|
||||
end
|
||||
local base_key = db.base[key]
|
||||
local real_MAJOR_VERSION
|
||||
for k,v in pairs(data) do
|
||||
if v == db then
|
||||
real_MAJOR_VERSION = k
|
||||
break
|
||||
end
|
||||
end
|
||||
if not real_MAJOR_VERSION then
|
||||
real_MAJOR_VERSION = LIBBABBLE_MAJOR
|
||||
end
|
||||
if base_key then
|
||||
warn(("%s: Translation %q not found for locale %q"):format(real_MAJOR_VERSION, key, GetLocale()))
|
||||
rawset(self, key, base_key)
|
||||
return base_key
|
||||
end
|
||||
warn(("%s: Translation %q not found."):format(real_MAJOR_VERSION, key))
|
||||
rawset(self, key, key)
|
||||
return key
|
||||
end }
|
||||
|
||||
local function initLookup(module, lookup)
|
||||
local db = tablesToDB[module]
|
||||
for k in pairs(lookup) do
|
||||
lookup[k] = nil
|
||||
end
|
||||
setmetatable(lookup, lookup_mt)
|
||||
tablesToDB[lookup] = db
|
||||
db.lookup = lookup
|
||||
return lookup
|
||||
end
|
||||
|
||||
local function initReverse(module, reverse)
|
||||
local db = tablesToDB[module]
|
||||
for k in pairs(reverse) do
|
||||
reverse[k] = nil
|
||||
end
|
||||
for k,v in pairs(db.current) do
|
||||
reverse[v] = k
|
||||
end
|
||||
tablesToDB[reverse] = db
|
||||
db.reverse = reverse
|
||||
db.reverseIterators = nil
|
||||
return reverse
|
||||
end
|
||||
|
||||
local prototype = {}
|
||||
local prototype_mt = {__index = prototype}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will warn but allow the code to pass through.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local BL = B:GetLookupTable()
|
||||
assert(BL["Some english word"] == "Some localized word")
|
||||
DoSomething(BL["Some english word that doesn't exist"]) -- warning!
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
local lookup = db.lookup
|
||||
if lookup then
|
||||
return lookup
|
||||
end
|
||||
return initLookup(self, {})
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local B_has = B:GetUnstrictLookupTable()
|
||||
assert(B_has["Some english word"] == "Some localized word")
|
||||
assert(B_has["Some english word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetUnstrictLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return db.current
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
* This is useful for checking if the base (English) table has a key, even if the localized one does not have it registered.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local B_hasBase = B:GetBaseLookupTable()
|
||||
assert(B_hasBase["Some english word"] == "Some english word")
|
||||
assert(B_hasBase["Some english word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetBaseLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return db.base
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
* This will return only one English word that it maps to, if there are more than one to check, see :GetReverseIterator("word")
|
||||
Returns:
|
||||
A lookup table for localized to english words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local BR = B:GetReverseLookupTable()
|
||||
assert(BR["Some localized word"] == "Some english word")
|
||||
assert(BR["Some localized word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetReverseLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
local reverse = db.reverse
|
||||
if reverse then
|
||||
return reverse
|
||||
end
|
||||
return initReverse(self, {})
|
||||
end
|
||||
local blank = {}
|
||||
local weakVal = {__mode='v'}
|
||||
--[[---------------------------------------------------------------------------
|
||||
Arguments:
|
||||
string - the localized word to chek for.
|
||||
Returns:
|
||||
An iterator to traverse all English words that map to the given key
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
for word in B:GetReverseIterator("Some localized word") do
|
||||
DoSomething(word)
|
||||
end
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetReverseIterator(key)
|
||||
local db = tablesToDB[self]
|
||||
local reverseIterators = db.reverseIterators
|
||||
if not reverseIterators then
|
||||
reverseIterators = setmetatable({}, weakVal)
|
||||
db.reverseIterators = reverseIterators
|
||||
elseif reverseIterators[key] then
|
||||
return pairs(reverseIterators[key])
|
||||
end
|
||||
local t
|
||||
for k,v in pairs(db.current) do
|
||||
if v == key then
|
||||
if not t then
|
||||
t = {}
|
||||
end
|
||||
t[k] = true
|
||||
end
|
||||
end
|
||||
reverseIterators[key] = t or blank
|
||||
return pairs(reverseIterators[key])
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Returns:
|
||||
An iterator to traverse all translations English to localized.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
for english, localized in B:Iterate() do
|
||||
DoSomething(english, localized)
|
||||
end
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:Iterate()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return pairs(db.current)
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to set the base table
|
||||
function prototype:SetBaseTranslations(base)
|
||||
local db = tablesToDB[self]
|
||||
local oldBase = db.base
|
||||
if oldBase then
|
||||
for k in pairs(oldBase) do
|
||||
oldBase[k] = nil
|
||||
end
|
||||
for k, v in pairs(base) do
|
||||
oldBase[k] = v
|
||||
end
|
||||
base = oldBase
|
||||
else
|
||||
db.base = base
|
||||
end
|
||||
for k,v in pairs(base) do
|
||||
if v == true then
|
||||
base[k] = k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function init(module)
|
||||
local db = tablesToDB[module]
|
||||
if db.lookup then
|
||||
initLookup(module, db.lookup)
|
||||
end
|
||||
if db.reverse then
|
||||
initReverse(module, db.reverse)
|
||||
end
|
||||
db.reverseIterators = nil
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to set the current table. if current is true, use the base table.
|
||||
function prototype:SetCurrentTranslations(current)
|
||||
local db = tablesToDB[self]
|
||||
if current == true then
|
||||
db.current = db.base
|
||||
else
|
||||
local oldCurrent = db.current
|
||||
if oldCurrent then
|
||||
for k in pairs(oldCurrent) do
|
||||
oldCurrent[k] = nil
|
||||
end
|
||||
for k, v in pairs(current) do
|
||||
oldCurrent[k] = v
|
||||
end
|
||||
current = oldCurrent
|
||||
else
|
||||
db.current = current
|
||||
end
|
||||
end
|
||||
init(self)
|
||||
end
|
||||
|
||||
for namespace, db in pairs(data) do
|
||||
setmetatable(db.module, prototype_mt)
|
||||
init(db.module)
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to create a new namespace.
|
||||
function LibBabble:New(namespace, minor)
|
||||
local module, oldminor = LibStub:NewLibrary(namespace, minor)
|
||||
if not module then
|
||||
return
|
||||
end
|
||||
|
||||
if not oldminor then
|
||||
local db = {
|
||||
module = module,
|
||||
}
|
||||
data[namespace] = db
|
||||
tablesToDB[module] = db
|
||||
else
|
||||
for k,v in pairs(module) do
|
||||
module[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(module, prototype_mt)
|
||||
|
||||
return module
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
## Interface: 30300
|
||||
## LoadOnDemand: 1
|
||||
## Title: Lib: Babble-Inventory-3.0
|
||||
## Notes: A library to help with localization of item types and subtypes.
|
||||
## Notes-esES: Una libreria para ayudar con la traduccion de tipos y subtipos de objetos.
|
||||
## Author: ckknight
|
||||
## X-eMail: ckknight@gmail.com
|
||||
## X-Category: Library
|
||||
## X-License: MIT
|
||||
## X-Curse-Packaged-Version: r101
|
||||
## X-Curse-Project-Name: LibBabble-Inventory-3.0
|
||||
## X-Curse-Project-ID: libbabble-inventory-3-0
|
||||
## X-Curse-Repository-ID: wow/libbabble-inventory-3-0/mainline
|
||||
|
||||
LibStub\LibStub.lua
|
||||
lib.xml
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 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
|
||||
@@ -0,0 +1,5 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="LibBabble-3.0.lua" />
|
||||
<Script file="LibBabble-Inventory-3.0.lua" />
|
||||
</Ui>
|
||||
@@ -0,0 +1,292 @@
|
||||
-- LibBabble-3.0 is hereby placed in the Public Domain
|
||||
-- Credits: ckknight
|
||||
local LIBBABBLE_MAJOR, LIBBABBLE_MINOR = "LibBabble-3.0", 2
|
||||
|
||||
local LibBabble = LibStub:NewLibrary(LIBBABBLE_MAJOR, LIBBABBLE_MINOR)
|
||||
if not LibBabble then
|
||||
return
|
||||
end
|
||||
|
||||
local data = LibBabble.data or {}
|
||||
for k,v in pairs(LibBabble) do
|
||||
LibBabble[k] = nil
|
||||
end
|
||||
LibBabble.data = data
|
||||
|
||||
local tablesToDB = {}
|
||||
for namespace, db in pairs(data) do
|
||||
for k,v in pairs(db) do
|
||||
tablesToDB[v] = db
|
||||
end
|
||||
end
|
||||
|
||||
local function warn(message)
|
||||
local _, ret = pcall(error, message, 3)
|
||||
geterrorhandler()(ret)
|
||||
end
|
||||
|
||||
local lookup_mt = { __index = function(self, key)
|
||||
local db = tablesToDB[self]
|
||||
local current_key = db.current[key]
|
||||
if current_key then
|
||||
self[key] = current_key
|
||||
return current_key
|
||||
end
|
||||
local base_key = db.base[key]
|
||||
local real_MAJOR_VERSION
|
||||
for k,v in pairs(data) do
|
||||
if v == db then
|
||||
real_MAJOR_VERSION = k
|
||||
break
|
||||
end
|
||||
end
|
||||
if not real_MAJOR_VERSION then
|
||||
real_MAJOR_VERSION = LIBBABBLE_MAJOR
|
||||
end
|
||||
if base_key then
|
||||
warn(("%s: Translation %q not found for locale %q"):format(real_MAJOR_VERSION, key, GetLocale()))
|
||||
rawset(self, key, base_key)
|
||||
return base_key
|
||||
end
|
||||
warn(("%s: Translation %q not found."):format(real_MAJOR_VERSION, key))
|
||||
rawset(self, key, key)
|
||||
return key
|
||||
end }
|
||||
|
||||
local function initLookup(module, lookup)
|
||||
local db = tablesToDB[module]
|
||||
for k in pairs(lookup) do
|
||||
lookup[k] = nil
|
||||
end
|
||||
setmetatable(lookup, lookup_mt)
|
||||
tablesToDB[lookup] = db
|
||||
db.lookup = lookup
|
||||
return lookup
|
||||
end
|
||||
|
||||
local function initReverse(module, reverse)
|
||||
local db = tablesToDB[module]
|
||||
for k in pairs(reverse) do
|
||||
reverse[k] = nil
|
||||
end
|
||||
for k,v in pairs(db.current) do
|
||||
reverse[v] = k
|
||||
end
|
||||
tablesToDB[reverse] = db
|
||||
db.reverse = reverse
|
||||
db.reverseIterators = nil
|
||||
return reverse
|
||||
end
|
||||
|
||||
local prototype = {}
|
||||
local prototype_mt = {__index = prototype}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will warn but allow the code to pass through.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local BL = B:GetLookupTable()
|
||||
assert(BL["Some english word"] == "Some localized word")
|
||||
DoSomething(BL["Some english word that doesn't exist"]) -- warning!
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
local lookup = db.lookup
|
||||
if lookup then
|
||||
return lookup
|
||||
end
|
||||
return initLookup(self, {})
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local B_has = B:GetUnstrictLookupTable()
|
||||
assert(B_has["Some english word"] == "Some localized word")
|
||||
assert(B_has["Some english word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetUnstrictLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return db.current
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
* This is useful for checking if the base (English) table has a key, even if the localized one does not have it registered.
|
||||
Returns:
|
||||
A lookup table for english to localized words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local B_hasBase = B:GetBaseLookupTable()
|
||||
assert(B_hasBase["Some english word"] == "Some english word")
|
||||
assert(B_hasBase["Some english word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetBaseLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return db.base
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Notes:
|
||||
* If you try to access a nonexistent key, it will return nil.
|
||||
* This will return only one English word that it maps to, if there are more than one to check, see :GetReverseIterator("word")
|
||||
Returns:
|
||||
A lookup table for localized to english words.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
local BR = B:GetReverseLookupTable()
|
||||
assert(BR["Some localized word"] == "Some english word")
|
||||
assert(BR["Some localized word that doesn't exist"] == nil)
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetReverseLookupTable()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
local reverse = db.reverse
|
||||
if reverse then
|
||||
return reverse
|
||||
end
|
||||
return initReverse(self, {})
|
||||
end
|
||||
local blank = {}
|
||||
local weakVal = {__mode='v'}
|
||||
--[[---------------------------------------------------------------------------
|
||||
Arguments:
|
||||
string - the localized word to chek for.
|
||||
Returns:
|
||||
An iterator to traverse all English words that map to the given key
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
for word in B:GetReverseIterator("Some localized word") do
|
||||
DoSomething(word)
|
||||
end
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:GetReverseIterator(key)
|
||||
local db = tablesToDB[self]
|
||||
local reverseIterators = db.reverseIterators
|
||||
if not reverseIterators then
|
||||
reverseIterators = setmetatable({}, weakVal)
|
||||
db.reverseIterators = reverseIterators
|
||||
elseif reverseIterators[key] then
|
||||
return pairs(reverseIterators[key])
|
||||
end
|
||||
local t
|
||||
for k,v in pairs(db.current) do
|
||||
if v == key then
|
||||
if not t then
|
||||
t = {}
|
||||
end
|
||||
t[k] = true
|
||||
end
|
||||
end
|
||||
reverseIterators[key] = t or blank
|
||||
return pairs(reverseIterators[key])
|
||||
end
|
||||
--[[---------------------------------------------------------------------------
|
||||
Returns:
|
||||
An iterator to traverse all translations English to localized.
|
||||
Example:
|
||||
local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
|
||||
for english, localized in B:Iterate() do
|
||||
DoSomething(english, localized)
|
||||
end
|
||||
-----------------------------------------------------------------------------]]
|
||||
function prototype:Iterate()
|
||||
local db = tablesToDB[self]
|
||||
|
||||
return pairs(db.current)
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to set the base table
|
||||
function prototype:SetBaseTranslations(base)
|
||||
local db = tablesToDB[self]
|
||||
local oldBase = db.base
|
||||
if oldBase then
|
||||
for k in pairs(oldBase) do
|
||||
oldBase[k] = nil
|
||||
end
|
||||
for k, v in pairs(base) do
|
||||
oldBase[k] = v
|
||||
end
|
||||
base = oldBase
|
||||
else
|
||||
db.base = base
|
||||
end
|
||||
for k,v in pairs(base) do
|
||||
if v == true then
|
||||
base[k] = k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function init(module)
|
||||
local db = tablesToDB[module]
|
||||
if db.lookup then
|
||||
initLookup(module, db.lookup)
|
||||
end
|
||||
if db.reverse then
|
||||
initReverse(module, db.reverse)
|
||||
end
|
||||
db.reverseIterators = nil
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to set the current table. if current is true, use the base table.
|
||||
function prototype:SetCurrentTranslations(current)
|
||||
local db = tablesToDB[self]
|
||||
if current == true then
|
||||
db.current = db.base
|
||||
else
|
||||
local oldCurrent = db.current
|
||||
if oldCurrent then
|
||||
for k in pairs(oldCurrent) do
|
||||
oldCurrent[k] = nil
|
||||
end
|
||||
for k, v in pairs(current) do
|
||||
oldCurrent[k] = v
|
||||
end
|
||||
current = oldCurrent
|
||||
else
|
||||
db.current = current
|
||||
end
|
||||
end
|
||||
init(self)
|
||||
end
|
||||
|
||||
for namespace, db in pairs(data) do
|
||||
setmetatable(db.module, prototype_mt)
|
||||
init(db.module)
|
||||
end
|
||||
|
||||
-- #NODOC
|
||||
-- modules need to call this to create a new namespace.
|
||||
function LibBabble:New(namespace, minor)
|
||||
local module, oldminor = LibStub:NewLibrary(namespace, minor)
|
||||
if not module then
|
||||
return
|
||||
end
|
||||
|
||||
if not oldminor then
|
||||
local db = {
|
||||
module = module,
|
||||
}
|
||||
data[namespace] = db
|
||||
tablesToDB[module] = db
|
||||
else
|
||||
for k,v in pairs(module) do
|
||||
module[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(module, prototype_mt)
|
||||
|
||||
return module
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
## Interface: 30300
|
||||
## LoadOnDemand: 1
|
||||
## Title: Lib: Babble-Zone-3.0
|
||||
## Notes: A library to help with localization of Zones.
|
||||
## Notes-deDE: BabbleLib ist eine Bibliothek, die bei der Lokalisierung helfen soll.
|
||||
## Notes-frFR: Une bibliothèque d'aide à la localisation.
|
||||
## Notes-esES: Una biblioteca para ayudar con las localizaciones.
|
||||
## Author: ckknight
|
||||
## X-eMail: ckknight@gmail.com
|
||||
## X-Category: Library
|
||||
## X-License: MIT
|
||||
## X-Curse-Packaged-Version: r277
|
||||
## X-Curse-Project-Name: LibBabble-Zone-3.0
|
||||
## X-Curse-Project-ID: libbabble-zone-3-0
|
||||
## X-Curse-Repository-ID: wow/libbabble-zone-3-0/mainline
|
||||
|
||||
LibStub\LibStub.lua
|
||||
lib.xml
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 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
|
||||
@@ -0,0 +1,5 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="LibBabble-3.0.lua" />
|
||||
<Script file="LibBabble-Zone-3.0.lua" />
|
||||
</Ui>
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
assert(LibStub, "LibDataBroker-1.1 requires LibStub")
|
||||
assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0")
|
||||
|
||||
local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 3)
|
||||
if not lib then return end
|
||||
oldminor = oldminor or 0
|
||||
|
||||
|
||||
lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib)
|
||||
lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {}
|
||||
local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks
|
||||
|
||||
if oldminor < 2 then
|
||||
lib.domt = {
|
||||
__metatable = "access denied",
|
||||
__index = function(self, key) return attributestorage[self] and attributestorage[self][key] end,
|
||||
}
|
||||
end
|
||||
|
||||
if oldminor < 3 then
|
||||
lib.domt.__newindex = function(self, key, value)
|
||||
if not attributestorage[self] then attributestorage[self] = {} end
|
||||
if attributestorage[self][key] == value then return end
|
||||
attributestorage[self][key] = value
|
||||
local name = namestorage[self]
|
||||
if not name then return end
|
||||
callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self)
|
||||
callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self)
|
||||
callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self)
|
||||
callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self)
|
||||
end
|
||||
end
|
||||
|
||||
if oldminor < 2 then
|
||||
function lib:NewDataObject(name, dataobj)
|
||||
if self.proxystorage[name] then return end
|
||||
|
||||
if dataobj then
|
||||
assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table")
|
||||
self.attributestorage[dataobj] = {}
|
||||
for i,v in pairs(dataobj) do
|
||||
self.attributestorage[dataobj][i] = v
|
||||
dataobj[i] = nil
|
||||
end
|
||||
end
|
||||
dataobj = setmetatable(dataobj or {}, self.domt)
|
||||
self.proxystorage[name], self.namestorage[dataobj] = dataobj, name
|
||||
self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj)
|
||||
return dataobj
|
||||
end
|
||||
end
|
||||
|
||||
if oldminor < 1 then
|
||||
function lib:DataObjectIterator()
|
||||
return pairs(self.proxystorage)
|
||||
end
|
||||
|
||||
function lib:GetDataObjectByName(dataobjectname)
|
||||
return self.proxystorage[dataobjectname]
|
||||
end
|
||||
|
||||
function lib:GetNameByDataObject(dataobject)
|
||||
return self.namestorage[dataobject]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
-- 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
|
||||
@@ -0,0 +1,13 @@
|
||||
## Interface: 20400
|
||||
## Title: Lib: LibStub
|
||||
## Notes: Universal Library Stub
|
||||
## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel
|
||||
## X-Website: http://jira.wowace.com/browse/LS
|
||||
## X-Category: Library
|
||||
## X-License: Public Domain
|
||||
## X-Curse-Packaged-Version: 1.0
|
||||
## X-Curse-Project-Name: LibStub
|
||||
## X-Curse-Project-ID: libstub
|
||||
## X-Curse-Repository-ID: wow/libstub/mainline
|
||||
|
||||
LibStub.lua
|
||||
Reference in New Issue
Block a user