Mercurial > wow > reaction
diff modules/FuBar_ReActionFu/lib/LibRock-1.0/LibRock-1.0.lua @ 30:0d95ce7a9ec2
- added Ace3 externs
- converted ReAction_ConfigUI to use blizzard interface addons panel via AceConfigDialog-3.0
- partially converted FuBar module to LibRock, deprecated it (going to remove it entirely later)
- cleaned up a couple other tidbits
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Wed, 02 Apr 2008 23:31:13 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/LibRock-1.0/LibRock-1.0.lua Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,2968 @@ +--[[ +Name: LibRock-1.0 +Revision: $Rev: 63317 $ +Developed by: ckknight (ckknight@gmail.com) +Website: http://www.wowace.com/ +Description: Library to allow for library and addon creation and easy table recycling functions. +License: LGPL v2.1 +]] + +local MAJOR_VERSION = "LibRock-1.0" +local MINOR_VERSION = tonumber(("$Revision: 63317 $"):match("(%d+)")) - 60000 + +local _G = _G +local GetLocale = _G.GetLocale +local CATEGORIES +if GetLocale() == "deDE" then + CATEGORIES = { + ["Action Bars"] = "Aktionsleisten", + ["Auction"] = "Auktion", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Schlachtfeld/PvP", + ["Buffs"] = "Stärkungszauber", + ["Chat/Communication"] = "Chat/Kommunikation", + ["Druid"] = "Druide", + ["Hunter"] = "Jäger", + ["Mage"] = "Magier", + ["Paladin"] = "Paladin", + ["Priest"] = "Priester", + ["Rogue"] = "Schurke", + ["Shaman"] = "Schamane", + ["Warlock"] = "Hexenmeister", + ["Warrior"] = "Krieger", + ["Healer"] = "Heiler", + ["Tank"] = "Tank", + ["Caster"] = "Zauberer", + ["Combat"] = "Kampf", + ["Compilations"] = "Zusammenstellungen", + ["Data Export"] = "Datenexport", + ["Development Tools"] = "Entwicklungstools", + ["Guild"] = "Gilde", + ["Frame Modification"] = "Frameveränderungen", + ["Interface Enhancements"] = "Interfaceverbesserungen", + ["Inventory"] = "Inventar", + ["Library"] = "Bibliotheken", + ["Map"] = "Karte", + ["Mail"] = "Post", + ["Miscellaneous"] = "Diverses", + ["Quest"] = "Quest", + ["Raid"] = "Schlachtzug", + ["Tradeskill"] = "Beruf", + ["UnitFrame"] = "Einheiten-Fenster", + } +elseif GetLocale() == "frFR" then + CATEGORIES = { + ["Action Bars"] = "Barres d'action", + ["Auction"] = "Hôtel des ventes", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Champs de bataille/JcJ", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Communication", + ["Druid"] = "Druide", + ["Hunter"] = "Chasseur", + ["Mage"] = "Mage", + ["Paladin"] = "Paladin", + ["Priest"] = "Prêtre", + ["Rogue"] = "Voleur", + ["Shaman"] = "Chaman", + ["Warlock"] = "Démoniste", + ["Warrior"] = "Guerrier", + ["Healer"] = "Soigneur", + ["Tank"] = "Tank", + ["Caster"] = "Casteur", + ["Combat"] = "Combat", + ["Compilations"] = "Compilations", + ["Data Export"] = "Exportation de données", + ["Development Tools"] = "Outils de développement", + ["Guild"] = "Guilde", + ["Frame Modification"] = "Modification des fenêtres", + ["Interface Enhancements"] = "Améliorations de l'interface", + ["Inventory"] = "Inventaire", + ["Library"] = "Bibliothèques", + ["Map"] = "Carte", + ["Mail"] = "Courrier", + ["Miscellaneous"] = "Divers", + ["Quest"] = "Quêtes", + ["Raid"] = "Raid", + ["Tradeskill"] = "Métiers", + ["UnitFrame"] = "Fenêtres d'unité", + } +elseif GetLocale() == "koKR" then + CATEGORIES = { + ["Action Bars"] = "액션바", + ["Auction"] = "경매", + ["Audio"] = "음향", + ["Battlegrounds/PvP"] = "전장/PvP", + ["Buffs"] = "버프", + ["Chat/Communication"] = "대화/의사소통", + ["Druid"] = "드루이드", + ["Hunter"] = "사냥꾼", + ["Mage"] = "마법사", + ["Paladin"] = "성기사", + ["Priest"] = "사제", + ["Rogue"] = "도적", + ["Shaman"] = "주술사", + ["Warlock"] = "흑마법사", + ["Warrior"] = "전사", + ["Healer"] = "힐러", + ["Tank"] = "탱커", + ["Caster"] = "캐스터", + ["Combat"] = "전투", + ["Compilations"] = "복합", + ["Data Export"] = "자료 출력", + ["Development Tools"] = "개발 도구", + ["Guild"] = "길드", + ["Frame Modification"] = "구조 변경", + ["Interface Enhancements"] = "인터페이스 강화", + ["Inventory"] = "인벤토리", + ["Library"] = "라이브러리", + ["Map"] = "지도", + ["Mail"] = "우편", + ["Miscellaneous"] = "기타", + ["Quest"] = "퀘스트", + ["Raid"] = "공격대", + ["Tradeskill"] = "전문기술", + ["UnitFrame"] = "유닛 프레임", + } +elseif GetLocale() == "zhTW" then + CATEGORIES = { + ["Action Bars"] = "動作列", + ["Auction"] = "拍賣", + ["Audio"] = "音效", + ["Battlegrounds/PvP"] = "戰場/PvP", + ["Buffs"] = "增益", + ["Chat/Communication"] = "聊天/通訊", + ["Druid"] = "德魯伊", + ["Hunter"] = "獵人", + ["Mage"] = "法師", + ["Paladin"] = "聖騎士", + ["Priest"] = "牧師", + ["Rogue"] = "盜賊", + ["Shaman"] = "薩滿", + ["Warlock"] = "術士", + ["Warrior"] = "戰士", + ["Healer"] = "治療者", + ["Tank"] = "坦克", + ["Caster"] = "施法者", + ["Combat"] = "戰鬥", + ["Compilations"] = "整合", + ["Data Export"] = "資料匯出", + ["Development Tools"] = "開發工具", + ["Guild"] = "公會", + ["Frame Modification"] = "框架修改", + ["Interface Enhancements"] = "介面增強", + ["Inventory"] = "庫存", + ["Library"] = "程式庫", + ["Map"] = "地圖", + ["Mail"] = "郵件", + ["Miscellaneous"] = "雜項", + ["Quest"] = "任務", + ["Raid"] = "團隊", + ["Tradeskill"] = "交易技能", + ["UnitFrame"] = "頭像框架", + } +elseif GetLocale() == "zhCN" then + CATEGORIES = { + ["Action Bars"] = "动作条", + ["Auction"] = "拍卖", + ["Audio"] = "音频", + ["Battlegrounds/PvP"] = "战场/PvP", + ["Buffs"] = "增益魔法", + ["Chat/Communication"] = "聊天/交流", + ["Druid"] = "德鲁伊", + ["Hunter"] = "猎人", + ["Mage"] = "法师", + ["Paladin"] = "圣骑士", + ["Priest"] = "牧师", + ["Rogue"] = "潜行者", + ["Shaman"] = "萨满祭司", + ["Warlock"] = "术士", + ["Warrior"] = "战士", + ["Healer"] = "治疗", + ["Tank"] = "坦克", + ["Caster"] = "远程输出", + ["Combat"] = "战斗", + ["Compilations"] = "编译", + ["Data Export"] = "数据导出", + ["Development Tools"] = "开发工具", + ["Guild"] = "公会", + ["Frame Modification"] = "框架修改", + ["Interface Enhancements"] = "界面增强", + ["Inventory"] = "背包", + ["Library"] = "库", + ["Map"] = "地图", + ["Mail"] = "邮件", + ["Miscellaneous"] = "杂项", + ["Quest"] = "任务", + ["Raid"] = "团队", + ["Tradeskill"] = "商业技能", + ["UnitFrame"] = "头像框架", + } +elseif GetLocale() == "esES" then + CATEGORIES = { + ["Action Bars"] = "Barras de Acción", + ["Auction"] = "Subasta", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Campos de Batalla/JcJ", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Comunicación", + ["Druid"] = "Druida", + ["Hunter"] = "Cazador", + ["Mage"] = "Mago", + ["Paladin"] = "Paladín", + ["Priest"] = "Sacerdote", + ["Rogue"] = "Pícaro", + ["Shaman"] = "Chamán", + ["Warlock"] = "Brujo", + ["Warrior"] = "Guerrero", + ["Healer"] = "Sanador", + ["Tank"] = "Tanque", + ["Caster"] = "Conjurador", + ["Combat"] = "Combate", + ["Compilations"] = "Compilaciones", + ["Data Export"] = "Exportar Datos", + ["Development Tools"] = "Herramientas de Desarrollo", + ["Guild"] = "Hermandad", + ["Frame Modification"] = "Modificación de Marcos", + ["Interface Enhancements"] = "Mejoras de la Interfaz", + ["Inventory"] = "Inventario", + ["Library"] = "Biblioteca", + ["Map"] = "Mapa", + ["Mail"] = "Correo", + ["Miscellaneous"] = "Misceláneo", + ["Quest"] = "Misión", + ["Raid"] = "Banda", + ["Tradeskill"] = "Habilidad de Comercio", + ["UnitFrame"] = "Marco de Unidades", + } +else -- enUS + CATEGORIES = { + ["Action Bars"] = "Action Bars", + ["Auction"] = "Auction", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Battlegrounds/PvP", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Communication", + ["Druid"] = "Druid", + ["Hunter"] = "Hunter", + ["Mage"] = "Mage", + ["Paladin"] = "Paladin", + ["Priest"] = "Priest", + ["Rogue"] = "Rogue", + ["Shaman"] = "Shaman", + ["Warlock"] = "Warlock", + ["Warrior"] = "Warrior", + ["Healer"] = "Healer", + ["Tank"] = "Tank", + ["Caster"] = "Caster", + ["Combat"] = "Combat", + ["Compilations"] = "Compilations", + ["Data Export"] = "Data Export", + ["Development Tools"] = "Development Tools", + ["Guild"] = "Guild", + ["Frame Modification"] = "Frame Modification", + ["Interface Enhancements"] = "Interface Enhancements", + ["Inventory"] = "Inventory", + ["Library"] = "Library", + ["Map"] = "Map", + ["Mail"] = "Mail", + ["Miscellaneous"] = "Miscellaneous", + ["Quest"] = "Quest", + ["Raid"] = "Raid", + ["Tradeskill"] = "Tradeskill", + ["UnitFrame"] = "UnitFrame", + } +end + +local select = _G.select +local tostring = _G.tostring +local pairs = _G.pairs +local ipairs = _G.ipairs +local error = _G.error +local setmetatable = _G.setmetatable +local getmetatable = _G.getmetatable +local type = _G.type +local pcall = _G.pcall +local next = _G.next +local tonumber = _G.tonumber +local strmatch = _G.strmatch +local table_remove = _G.table.remove +local debugstack = _G.debugstack +local LoadAddOn = _G.LoadAddOn +local GetAddOnInfo = _G.GetAddOnInfo +local GetAddOnMetadata = _G.GetAddOnMetadata +local GetNumAddOns = _G.GetNumAddOns +local DisableAddOn = _G.DisableAddOn +local EnableAddOn = _G.EnableAddOn +local IsAddOnLoadOnDemand = _G.IsAddOnLoadOnDemand +local IsLoggedIn = _G.IsLoggedIn +local geterrorhandler = _G.geterrorhandler +local assert = _G.assert +local collectgarbage = _G.collectgarbage +local table_sort = _G.table.sort +local table_concat = _G.table.concat + +-- #AUTODOC_NAMESPACE Rock + + +local LibStub = _G.LibStub + +local Rock = LibStub:GetLibrary(MAJOR_VERSION, true) or _G.Rock +local oldRock +if not Rock then + Rock = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION) + if not Rock then + return + end + Rock.name = MAJOR_VERSION +else + Rock, oldRock = Rock:NewLibrary(MAJOR_VERSION, MINOR_VERSION) + if not Rock then + return + end +end +_G.Rock = Rock + +local L = setmetatable({}, {__index=function(self,key) self[key] = key; return key end}) +if GetLocale() == "zhCN" then + L["Advanced options"] = "高级选项" + L["Advanced options for developers and power users."] = "开发者与高级用户的高级选项" + L["Unit tests"] = "框体测试" + L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "开启框体测试,仅供开发者使用。\n\n需要重载用户界面。" + L["Contracts"] = "侦错协定" + L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."] = "启用侦错协定,这是给插件作者用来通报错误所使用。" + L["Reload UI"] = "重载UI" + L["Reload the User Interface for some changes to take effect."] = "部分功能更改需要重载用户界面才会生效。" + L["Reload"] = "重载" + L["Give donation"] = "捐赠" + L["Donate"] = "捐赠" + L["Give a much-needed donation to the author of this addon."] = "给插件作者捐赠支持插件开发。" + L["File issue"] = "通报错误" + L["Report"] = "报告" + L["File a bug or request a new feature or an improvement to this addon."] = "发送错误报告或请求新功能及要改进的部分。" + L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C复制网址,Alt-Tab切换到桌面,打开浏览器,在地址栏贴上网址。" + L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C复制网址,Cmd-Tab切换到电脑桌面,打开浏览器,在地址栏贴上网址。" + L["Enabled"] = "开启" + L["Enable or disable this addon."] = "启用这个插件。" + +elseif GetLocale() == "zhTW" then + L["Advanced options"] = "進階選項" + L["Advanced options for developers and power users."] = "插件作者、進階用戶選項" + L["Unit tests"] = "單元測試" + L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "啟用單元測試,這是給插件作者使用的功能。\n\n需要重載介面才能使用。" + L["Contracts"] = "偵錯協定" + L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."] = "啟用偵錯協定,這是給插件作者用來通報錯誤所使用。" + L["Reload UI"] = "重載介面" + L["Reload the User Interface for some changes to take effect."] = "重新載入使用者介面,部分功能才會生效。" + L["Reload"] = "重載" + L["Give donation"] = "捐贈" + L["Donate"] = "捐贈" + L["Give a much-needed donation to the author of this addon."] = "捐贈金錢給插件作者。" + L["File issue"] = "通報錯誤" + L["Report"] = "報告" + L["File a bug or request a new feature or an improvement to this addon."] = "發出錯誤報告或請求新功能及要改進的部分。" + L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C複製網址,Alt-Tab切換到電腦桌面,打開瀏覽器,在網址列貼上網址。" + L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C複製網址,Cmd-Tab切換到電腦桌面,打開瀏覽器,在網址列貼上網址。" + L["Enabled"] = "啟用" + L["Enable or disable this addon."] = "啟用這個插件。" +elseif GetLocale() == "koKR" then + L["Advanced options"] = "상세 옵션" + L["Advanced options for developers and power users."] = "개발자와 파워 사용자를 위한 상세 옵션입니다." + L["Unit tests"] = "유닛 테스트" + L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "유닛 테스트를 사용합니다. 이것은 개발자만을 위한 옵션입니다.\n\n변경된 결과를 적용하기 위해 당신의 UI를 재실행 합니다." + L["Contracts"] = "계약" + L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."] = "계약을 사용합니다. 이것은 개발자와 버그 파일을 알릴 분이면 누구나 사용 가능합니다. 계약이 가능하지 않으면 버그 파일을 보내지 마십시오. 이것은 당신의 애드온 속도를 약간 떨어뜨립니다." + L["Reload UI"] = "UI 재실행" + L["Reload the User Interface for some changes to take effect."] = "변경된 결과를 적용하기 위해 사용자 인터페이스를 재실행합니다." + L["Reload"] = "재실행" + L["Give donation"] = "기부" + L["Donate"] = "기부" + L["Give a much-needed donation to the author of this addon."] = "이 애드온의 제작자에게 필요한 기부를 합니다." + L["File issue"] = "파일 이슈" + L["Report"] = "보고" + L["File a bug or request a new feature or an improvement to this addon."] = "버그 파일을 알리거나 새로운 기능 또는 이 애드온에 대한 개선을 부탁합니다." + L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C로 복사합니다. Alt-Tab 눌려 게임으로 부터 나간후 웹 브라우저를 엽니다. 복사된 링크를 주소 창에 붙여넣기 합니다." + L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C로 복사합니다. Cmd-Tab 눌려 게임으로 부터 나간후 웹 브라우저를 엽니다. 복사된 링크를 주소 창에 붙여넣기 합니다." + L["Enabled"] = "사용" + L["Enable or disable this addon."] = "이 애드온을 사용하거나 사용하지 않습니다." +elseif GetLocale() == "frFR" then + L["Advanced options"] = "Options avancées" + L["Advanced options for developers and power users."] = "Options avancées à l'attention des développeurs et des utilisateurs expérimentés." + L["Reload UI"] = "Recharger IU" + L["Reload the User Interface for some changes to take effect."] = "Recharge l'interface utilisateur afin que certains changements prennent effet." + L["Reload"] = "Recharger" + L["Give donation"] = "Faire un don" + L["Donate"] = "Don" + L["Give a much-needed donation to the author of this addon."] = "Permet de faire un don bien mérité à l'auteur de cet addon." + L["File issue"] = "Problème" + L["Report"] = "Signaler" + L["File a bug or request a new feature or an improvement to this addon."] = "Permet de signaler un bogue ou de demander une amélioration à cet addon." + L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C pour copier, puis Alt-Tab pour sortir du jeu. Ouvrez votre navigateur internet et collez le lien dans la barre d'adresse." + L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C pour copier, puis Alt-Tab pour sortir du jeu. Ouvrez votre navigateur internet et collez le lien dans la barre d'adresse." + L["Enabled"] = "Activé" + L["Enable or disable this addon."] = "Active ou désactive cet addon." +end + +local isStandalone = debugstack():match("[Oo%.][Nn%.][Ss%.]\\([^\\]+)\\") == MAJOR_VERSION or nil +local unitTestDB, enableContracts + +local weakKey = { __mode = 'k' } + +-- frame to manage events from +Rock.frame = oldRock and oldRock.frame or _G.CreateFrame("Frame") +local frame = Rock.frame +-- dict of libraries in { ["major"] = object } form +Rock.libraries = oldRock and oldRock.libraries or { [MAJOR_VERSION] = Rock } +local libraries = Rock.libraries +-- set of libraries which have gone through the finalization process in { [object] = true } form +Rock.finalizedLibraries = setmetatable(oldRock and oldRock.finalizedLibraries or { }, weakKey) +local finalizedLibraries = Rock.finalizedLibraries +-- set of libraries which have been tried to be loaded. +Rock.scannedLibraries = oldRock and oldRock.scannedLibraries or {} +local scannedLibraries = Rock.scannedLibraries +-- exportedMethods[library] = { "method1", "method2" } +Rock.exportedMethods = setmetatable(oldRock and oldRock.exportedMethods or {}, weakKey) +local exportedMethods = Rock.exportedMethods +-- mixinToObject[mixin][object] = true +Rock.mixinToObject = setmetatable(oldRock and oldRock.mixinToObject or {}, weakKey) +local mixinToObject = Rock.mixinToObject +-- dict of addons in { ["name"] = object } form +Rock.addons = oldRock and oldRock.addons or {} +local addons = Rock.addons +-- set of libraries that should be finalized before ADDON_LOADED. +Rock.pendingLibraries = setmetatable(oldRock and oldRock.pendingLibraries or { }, weakKey) +local pendingLibraries = Rock.pendingLibraries +-- list of addons in order of created that need to be initialized by ADDON_LOADED. +Rock.pendingAddons = oldRock and oldRock.pendingAddons or {} +local pendingAddons = Rock.pendingAddons +-- dictionary of addons to their folder names +Rock.addonToFolder = oldRock and oldRock.addonToFolder or {} +local addonToFolder = Rock.addonToFolder +-- set of folders which have been loaded +Rock.foldersLoaded = oldRock and oldRock.foldersLoaded or {} +local foldersLoaded = Rock.foldersLoaded +-- list of addons in order of created that need to be enabled by PLAYER_LOGIN. +Rock.pendingAddonsEnable = oldRock and oldRock.pendingAddonsEnable or {} +local pendingAddonsEnable = Rock.pendingAddonsEnable +-- set of addons which have been enabled at least once. +Rock.addonsAlreadyEnabled = oldRock and oldRock.addonsAlreadyEnabled or {} +local addonsAlreadyEnabled = Rock.addonsAlreadyEnabled +-- set of addons which have no database and are set to be inactive. +Rock.inactiveAddons = oldRock and oldRock.inactiveAddons or {} +local inactiveAddons = Rock.inactiveAddons +-- set of addons which are currently enabled (not necessarily should be) +Rock.currentlyEnabledAddons = oldRock and oldRock.currentlyEnabledAddons or {} +local currentlyEnabledAddons = Rock.currentlyEnabledAddons +-- dictionary of namespace to list of functions which will be run. +Rock.unitTests = oldRock and oldRock.unitTests or {} +local unitTests = Rock.unitTests +-- metatable for addons +Rock.addon_mt = oldRock and oldRock.addon_mt or {} +local addon_mt = Rock.addon_mt +for k in pairs(addon_mt) do + addon_mt[k] = nil +end +function addon_mt:__tostring() + return tostring(self.name) +end + +local function better_tostring(self) + if type(self) == "table" and self.name then + return tostring(self.name) + end + return tostring(self) +end + +local function figureCurrentAddon(pos) + local stack = debugstack(pos+1, 1, 0) + local folder = stack:match("[Oo%.][Nn%.][Ss%.]\\([^\\]+)\\") + if folder then + return folder + end + + local partFolder = stack:match("...([^\\]+)\\") + if partFolder then + local partFolder_len = #partFolder + for i = 1, GetNumAddOns() do + local name = GetAddOnInfo(i) + if #name >= partFolder_len then + local partName = name:sub(-partFolder_len) + if partName == partFolder then + return name + end + end + end + end + return nil +end + +--[[--------------------------------------------------------------------------- +Returns: + string - the localized name of the given category. +Arguments: + string - the English name of the category. +Example: + local uf = Rock:GetLocalizedCategory("UnitFrame") +-----------------------------------------------------------------------------]] +function Rock:GetLocalizedCategory(name) + if type(name) ~= "string" then + error(("Bad argument #2 to `GetLocalizedCategory'. Expected %q, got %q."):format("string", type(name)), 2) + end + local cat = CATEGORIES[name] + if cat then + return cat + end + local name_lower = name:lower() + for k in pairs(CATEGORIES) do + if k:lower() == name_lower then + return k + end + end + return _G.UNKNOWN or "Unknown" +end + +local weak = {__mode = 'kv'} + +Rock.recycleData = oldRock and oldRock.recycleData or {} +local recycleData = Rock.recycleData +if recycleData.pools then + setmetatable(recycleData.pools, weak) +end +if recycleData.debugPools then + setmetatable(recycleData.debugPools, weak) +end +if recycleData.newList then + setmetatable(recycleData.newList, weak) +end +if recycleData.newDict then + setmetatable(recycleData.newDict, weak) +end +if recycleData.newSet then + setmetatable(recycleData.newSet, weak) +end +if recycleData.del then + setmetatable(recycleData.del, weak) +end + +local tmp = {} +local function myUnpack(t, start) + if not start then + start = 1 + end + local value = t[start] + if value == nil then + return + end + t[start] = nil + return value, myUnpack(t, start+1) +end + +--[[--------------------------------------------------------------------------- +Notes: + * Returns functions for the specified namespace based on what is provided. + * function types: + ; "newList" : to create a list + ; "newDict" : to create a dictionary + ; "newSet" : to create a set + ; "del" : to delete a table + ; "unpackListAndDel" : deletes a table and returns what its contents were as a list, in order. + ; "unpackSetAndDel" : deletes a table and returns what its contents were as a set, in no particular order. + ; "unpackDictAndDel" : deletes a table and returns what its contents were as a dictionary, in no particular order. + * If you provide "Debug" as the last argument, then the namespace can be debugged with ''':DebugRecycle''' + * It is '''not recommended''' to use table recycling with tables that have more than 128 keys, as it is typically faster to let lua's garbage collector handle it. +Arguments: + string - the namespace. ''Note: this doesn't necessarily have to be a string.'' +Example: + local newList, newDict, newSet, del, unpackListAndDel, unpackSetAndDel, unpackDictAndDel = Rock:GetRecyclingFunctions("MyNamespace", "newList", "newDict", "newSet", "del", "unpackListAndDel", "unpackSetAndDel", "unpackDictAndDel") + + local t = newList('alpha', 'bravo') -- same as t = {'alpha', 'bravo'} + local u = newDict('alpha', 'bravo') -- same as t = {['alpha'] = 'bravo'} + local v = newSet('alpha', 'bravo') -- same as t = {['alpha'] = true, ['bravo'] = true} + t = del(t) -- you want to clear your reference as well as deleting. + u = del(u) + v = del(v) + + -- for debugging + local newList = Rock:GetRecyclingFunctions("MyNamespace", "newList", "Debug") + local t = newList() + Rock:DebugRecycle("MyNamespace") + t = del(t) + + -- unpacking functions + unpackListAndDel(newList(...)) => ... + unpackSetAndDel(newSet(...)) => ... + unpackDictAndDel(newDict(...)) => ... + newList(unpackListAndDel(t)) => t + newSet(unpackSetAndDel(t)) => t + newDict(unpackDictAndDel(t)) => t + -- as you can see, they are inverses of each other. +-----------------------------------------------------------------------------]] +function Rock:GetRecyclingFunctions(namespace, ...) + local pools = recycleData.pools + if not pools then + pools = setmetatable({}, weak) + recycleData.pools = pools + end + if namespace == "newList" or namespace == "newSet" or namespace == "newDict" or namespace == "del" or namespace == "unpackListAndDel" or namespace == "unpackSetAndDel" or namespace == "unpackDictAndDel" then + error(("Bad argument #2 to `GetRecyclingFunctions'. Cannot be %q"):format(namespace), 2) + end + local pool = pools[namespace] + if not pool then + pool = setmetatable({}, weak) + pools[namespace] = pool + end + local n = select('#', ...) + local debug = select(n, ...) == "Debug" + if debug then + n = n - 1 + local debugPools = recycleData.debugPools + if not debugPools then + debugPools = setmetatable({}, weak) + recycleData.debugPools = debugPools + end + debug = debugPools[namespace] + if not debug then + debug = { num = 0 } + debugPools[namespace] = debug + end + elseif recycleData.debugPools and recycleData.debugPools[namespace] then + debug = recycleData.debugPools[namespace] + end + for i = 1, n do + local func = select(i, ...) + local recycleData_func = recycleData[func] + if not recycleData_func then + recycleData_func = setmetatable({}, weak) + recycleData[func] = recycleData_func + end + if func == "newList" then + local newList = recycleData_func[namespace] + if not newList then + function newList(...) + local t = next(pool) + local n = select('#', ...) + if t then + pool[t] = nil + for i = 1, n do + t[i] = select(i, ...) + end + else + t = { ... } + end + + if debug then + debug[t] = debugstack(2) + debug.num = debug.num + 1 + end + + return t, n + end + recycleData_func[namespace] = newList + end + tmp[i] = newList + elseif func == "newDict" then + local newDict = recycleData_func[namespace] + if not newDict then + function newDict(...) + local t = next(pool) + if t then + pool[t] = nil + else + t = {} + end + + for i = 1, select('#', ...), 2 do + t[select(i, ...)] = select(i+1, ...) + end + + if debug then + debug[t] = debugstack(2) + debug.num = debug.num + 1 + end + + return t + end + recycleData_func[namespace] = newDict + end + tmp[i] = newDict + elseif func == "newSet" then + local newSet = recycleData_func[namespace] + if not newSet then + function newSet(...) + local t = next(pool) + if t then + pool[t] = nil + else + t = {} + end + + for i = 1, select('#', ...) do + t[select(i, ...)] = true + end + + if debug then + debug[t] = debugstack(2) + debug.num = debug.num + 1 + end + + return t + end + recycleData_func[namespace] = newSet + end + tmp[i] = newSet + elseif func == "del" then + local del = recycleData_func[namespace] + if not del then + function del(t) + if not t then + error(("Bad argument #1 to `del'. Expected %q, got %q."):format("table", type(t)), 2) + end + if pool[t] then + local _, ret = pcall(error, "Error, double-free syndrome.", 3) + geterrorhandler()(ret) + end + setmetatable(t, nil) + for k in pairs(t) do + t[k] = nil + end + t[true] = true + t[true] = nil + pool[t] = true + + if debug then + debug[t] = nil + debug.num = debug.num - 1 + end + return nil + end + recycleData_func[namespace] = del + end + tmp[i] = del + elseif func == "unpackListAndDel" then + local unpackListAndDel = recycleData_func[namespace] + if not unpackListAndDel then + local function f(t, start, finish) + if start > finish then + for k in pairs(t) do + t[k] = nil + end + t[true] = true + t[true] = nil + pool[t] = true + return + end + return t[start], f(t, start+1, finish) + end + function unpackListAndDel(t, start, finish) + if not t then + error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2) + end + if not start then + start = 1 + end + if not finish then + finish = #t + end + setmetatable(t, nil) + if debug then + debug[t] = nil + debug.num = debug.num - 1 + end + return f(t, start, finish) + end + end + tmp[i] = unpackListAndDel + elseif func == "unpackSetAndDel" then + local unpackSetAndDel = recycleData_func[namespace] + if not unpackSetAndDel then + local function f(t, current) + current = next(t, current) + if current == nil then + for k in pairs(t) do + t[k] = nil + end + t[true] = true + t[true] = nil + pool[t] = true + return + end + return current, f(t, current) + end + function unpackSetAndDel(t) + if not t then + error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2) + end + setmetatable(t, nil) + if debug then + debug[t] = nil + debug.num = debug.num - 1 + end + return f(t, nil) + end + end + tmp[i] = unpackSetAndDel + elseif func == "unpackDictAndDel" then + local unpackDictAndDel = recycleData_func[namespace] + if not unpackDictAndDel then + local function f(t, current) + local value + current, value = next(t, current) + if current == nil then + for k in pairs(t) do + t[k] = nil + end + t[true] = true + t[true] = nil + pool[t] = true + return + end + return current, value, f(t, current) + end + function unpackDictAndDel(t) + if not t then + error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2) + end + setmetatable(t, nil) + if debug then + debug[t] = nil + debug.num = debug.num - 1 + end + return f(t, nil) + end + end + tmp[i] = unpackDictAndDel + else + error(("Bad argument #%d to `GetRecyclingFunctions': %q, %q, %q, %q, %q, %q, or %q expected, got %s"):format(i+2, "newList", "newDict", "newSet", "del", "unpackListAndDel", "unpackSetAndDel", "unpackDictAndDel", type(func) == "string" and ("%q"):format(func) or tostring(func)), 2) + end + end + return myUnpack(tmp) +end + +--[[--------------------------------------------------------------------------- +Notes: + * Prints information about the specified recycling namespace, including what tables are still in play and where they come from and how many there are. + * This goes in tandem with ''':GetRecyclingFunctions''' +Arguments: + string - the namespace. ''Note: this doesn't necessarily have to be a string.'' +Example: + local newList = Rock:GetRecyclingFunctions("MyNamespace", "newList", "Debug") + local t = newList() + Rock:DebugRecycle("MyNamespace") + t = del(t) +-----------------------------------------------------------------------------]] +function Rock:DebugRecycle(namespace) + local debug = recycleData.debugPools and recycleData.debugPools[namespace] + if not debug then + return + end + for k, v in pairs(debug) do + if k ~= "num" then + _G.DEFAULT_CHAT_FRAME:AddMessage(v) + _G.DEFAULT_CHAT_FRAME:AddMessage("------") + end + end + _G.DEFAULT_CHAT_FRAME:AddMessage(("%s: %d tables in action."):format(tostring(namespace), debug.num)) +end + +local newList, del, unpackListAndDel, unpackDictAndDel = Rock:GetRecyclingFunctions(MAJOR_VERSION, "newList", "del", "unpackListAndDel", "unpackDictAndDel") + +--[[--------------------------------------------------------------------------- +Notes: + * Adds a unit test for the specified namespace + * The function provided is called, and it should be where tests are performed, if a problem occurs, an error should fire. If no problems occur, it should return silently. + * You can have as many tests per namespace as you want. +Arguments: + string - the namespace. + function - the function to call. +Example: + Rock:AddUnitTest("LibMonkey-1.0", function() + local LibMonkey = Rock("LibMonkey-1.0") + assert(LibMonkey:Fling() == "Poo") + end) +-----------------------------------------------------------------------------]] +function Rock:AddUnitTest(namespace, func) + if not isStandalone then + return + end + if type(namespace) ~= "string" then + error(("Bad argument #2 to `AddUnitTest'. Expected %q, got %q."):format("string", type(namespace)), 2) + end + if namespace:find("^Lib[A-Z]") then + local addon = figureCurrentAddon(2) + if addon ~= namespace then + return + end + end + if type(func) ~= "function" then + error(("Bad argument #3 to `AddUnitTest'. Expected %q, got %q."):format("function", type(func)), 2) + end + local addon = figureCurrentAddon(2) + if libraries[namespace] and addon ~= namespace then + -- only work on standalone libraries. + return + end + local unitTests_namespace = unitTests[namespace] + if not unitTests_namespace then + unitTests_namespace = newList() + unitTests[namespace] = unitTests_namespace + end + if not unitTests_namespace.addon then + unitTests_namespace.addon = addon + end + if unitTestDB and not unitTestDB[namespace] then + return + end + unitTests_namespace[#unitTests_namespace+1] = func +end + +local LibRockEvent +local LibRockModuleCore +local OpenDonationFrame, OpenIssueFrame +function Rock:OnLibraryLoad(major, library) + if major == "LibRockEvent-1.0" then + LibRockEvent = library + LibRockEvent:Embed(Rock) + elseif major == "LibRockModuleCore-1.0" then + LibRockModuleCore = library + elseif major == "LibRockConfig-1.0" then + if isStandalone then + library.rockOptions.args.advanced = { + type = 'group', + groupType = 'inline', + name = L["Advanced options"], + desc = L["Advanced options for developers and power users."], + order = -1, + args = { + unitTests = { + type = 'multichoice', + name = L["Unit tests"], + desc = L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."], + get = function(key) + return unitTestDB[key] + end, + set = function(key, value) + unitTestDB[key] = value or nil + end, + choices = function() + local t = newList() + for k in pairs(unitTests) do + t[k] = k + end + return "@dict", unpackDictAndDel(t) + end + }, + contracts = { + type = 'boolean', + name = L["Contracts"], + desc = L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."], + get = function() + return enableContracts + end, + set = function(value) + _G.LibRock_1_0DB.contracts = value or nil + enableContracts = value + end, + } + } + } + end + library.rockOptions.args.reloadui = { + type = 'execute', + name = L["Reload UI"], + desc = L["Reload the User Interface for some changes to take effect."], + buttonText = L["Reload"], + func = function() + _G.ReloadUI() + end, + order = -2, + } + Rock.donate = "Paypal:ckknight AT gmail DOT com" + library.rockOptions.args.donate = { + type = 'execute', + name = L["Give donation"], + buttonText = L["Donate"], + desc = L["Give a much-needed donation to the author of this addon."], + func = OpenDonationFrame, + passValue = Rock, + order = -3, + } + Rock.issueTracker = "Wowace:10027" + library.rockOptions.args.issue = { + type = 'execute', + name = L["File issue"], + buttonText = L["Report"], + desc = L["File a bug or request a new feature or an improvement to this addon."], + func = OpenIssueFrame, + passValue = Rock, + order = -4, + } + end +end + +addon_mt.__index = {} +local addon_mt___index = addon_mt.__index +--[[--------------------------------------------------------------------------- +#FORCE_DOC +Notes: + * This is exported to all addons. + * This information is retrieved from LibRockModuleCore-1.0 if it is a module, otherwise from LibRockDB-1.0 if it uses that as a mixin, otherwise it keeps a variable locally. +Returns: + boolean - whether the addon is in an active state or not. +Example: + local active = MyAddon:IsActive() +-----------------------------------------------------------------------------]] +function addon_mt___index:IsActive() + if LibRockModuleCore then + local core = LibRockModuleCore:HasModule(self) + if core then + return core:IsModuleActive(self) + end + end + + local self_db = self.db + if self_db then + local disabled + local self_db_raw = self_db.raw + if self_db_raw then + local self_db_raw_disabled = self_db_raw.disabled + if self_db_raw_disabled then + local profile = type(self.GetProfile) == "function" and select(2, self:GetProfile()) or false + disabled = self_db_raw_disabled[profile] + end + else + return false + end + return not disabled + end + + return not inactiveAddons[self] +end +--[[--------------------------------------------------------------------------- +#FORCE_DOC +Notes: + * This is exported to all addons. + * If it enables the addon, it will call :OnEnable(first) on the addon and :OnEmbedEnable(addon, first) on all its mixins. + * If it disables the addon, it will call :OnDisable(first) on the addon and :OnEmbedDisable(addon, first) on all its mixins. + * This information is stored by LibRockModuleCore-1.0 if it is a module, otherwise from LibRockDB-1.0 if it uses that as a mixin, otherwise it keeps a variable locally. +Arguments: + [optional] boolean - whether the addon should be in an active state or not. Default: not :IsActive() +Returns: + boolean - whether the addon is in an active state or not. +Example: + MyAddon:ToggleActive() -- switch + MyAddon:ToggleActive(true) -- force on + MyAddon:ToggleActive(false) -- force off +-----------------------------------------------------------------------------]] +function addon_mt___index:ToggleActive(state) + if state and state ~= true then + error(("Bad argument #2 to `ToggleActive'. Expected %q or %q, got %q."):format("boolean", "nil", type(state)), 2) + end + if LibRockModuleCore then + local core = LibRockModuleCore:HasModule(self) + if core then + return core:ToggleModuleActive(self, state) + end + end + + local self_db = self.db + if self_db then + local self_db_raw = self_db.raw + if not self_db_raw then + error("Error saving to database with `ToggleActive'. db.raw not available.", 2) + end + local self_db_raw_disabled = self_db_raw.disabled + if not self_db_raw_disabled then + self_db_raw_disabled = newList() + self_db_raw.disabled = self_db_raw_disabled + end + local profile = type(self.GetProfile) == "function" and select(2, self:GetProfile()) or false + if state == nil then + state = not not self_db_raw_disabled[profile] + elseif (not self_db_raw_disabled[profile]) == state then + return + end + self_db_raw_disabled[profile] = not state or nil + if next(self_db_raw_disabled) == nil then + self_db_raw.disabled = del(self_db_raw_disabled) + end + else + if state == nil then + state = not not inactiveAddons[self] + elseif (not inactiveAddons[self]) == state then + return + end + inactiveAddons[self] = not state or nil + end + + Rock:RecheckEnabledStates() + + return state +end + +local function noop() end + +do + local preconditions = setmetatable({}, weakKey) + local postconditions = setmetatable({}, weakKey) + local postconditionsOld = setmetatable({}, weakKey) + + local currentMethod = nil + + local function hook(object, method) + local object_method = object[method] + object[method] = function(...) + local pre = preconditions[object_method] + local post = postconditions[object_method] + if pre then + local old_currentMethod = currentMethod + currentMethod = method + pre(...) + currentMethod = old_currentMethod + end + if not post then + return object_method(...) + end + local oldFunc = postconditionsOld[object_method] + local old + if oldFunc then + old = newList() + oldFunc(old, ...) + end + + local old_currentMethod = currentMethod + currentMethod = nil + local ret, n = newList(object_method(...)) + + currentMethod = method + if old then + post(old, ret, ...) + old = del(old) + else + post(ret, ...) + end + currentMethod = old_currentMethod + return unpackListAndDel(ret, 1, n) + end + end + + local function precondition(object, method, func) + if type(object) ~= "table" then + error(("Bad argument #1 to `precondition'. Expected %q, got %q."):format("table", type(object)), 2) + end + if type(object[method]) ~= "function" then + error(("Method %q not found on object %s. Expected %q, got %q."):format(tostring(method), tostring(object), "function", type(object[method])), 2) + end + if type(func) ~= "function" then + error(("Bad argument #3 to `precondition'. Expected %q, got %q."):format("function", type(func)), 2) + end + + local object_method = object[method] + if preconditions[object_method] then + error("Cannot call `preconditon' on the same method twice.", 2) + end + preconditions[object_method] = func + + if not postconditions[object_method] then + hook(object, method) + end + end + + local function postcondition(object, method, func, fillOld) + if type(object) ~= "table" then + error(("Bad argument #1 to `postcondition'. Expected %q, got %q."):format("table", type(object)), 2) + end + if type(object[method]) ~= "function" then + error(("Method %q not found on object %s. Expected %q, got %q."):format(tostring(method), tostring(object), "function", type(object[method])), 2) + end + if type(func) ~= "function" then + error(("Bad argument #3 to `postcondition'. Expected %q, got %q."):format("function", type(func)), 2) + end + if fillOld and type(fillOld) ~= "function" then + error(("Bad argument #4 to `postcondition'. Expected %q or %q, got %q."):format("function", "nil", type(func)), 2) + end + + local object_method = object[method] + if postconditions[object_method] then + error("Cannot call `postcondition' on the same method twice.", 2) + end + postconditions[object_method] = func + postconditionsOld[object_method] = fillOld + + if not preconditions[object_method] then + hook(object, method) + end + end + + local function argCheck(value, position, ...) + if not currentMethod then + error("Cannot call `argCheck' outside of a pre/post-condition.", 2) + end + if type(position) ~= "number" then + error(("Bad argument #2 to `argCheck'. Expected %q, got %q"):format("number", type(position)), 2) + end + local type_value = type(value) + for i = 1, select('#', ...) do + local v = select(i, ...) + if type(v) ~= "string" then + error(("Bad argument #%d to `argCheck'. Expected %q, got %q"):format(i+1, "string", type(v)), 2) + end + if v == type_value then + return + end + end + local t = newList(...) + t[#t] = nil + for i,v in ipairs(t) do + t[i] = ("%q"):format(v) + end + local s + if #t == 0 then + s = ("%q"):format((...)) + elseif #t == 1 then + s = ("%q or %q"):format(...) + else + s = table_concat(t, ", ") .. ", or " .. ("%q"):format(select(#t+1, ...)) + end + t = del(t) + + error(("Bad argument #%d to `%s'. Expected %s, got %q."):format(position, tostring(currentMethod), s, type_value), 4) + end + + --[[--------------------------------------------------------------------------- + Notes: + * Returns functions for the specified namespace based on what is provided. + * function types: + ; "precondition" : to set the pre-condition for a method. + ; "postcondition" : to set the post-condition for a method. + ; "argCheck" : to check the type of an argument, to be executed within a pre-condition. + * preconditon is in the form of <tt>precondition(object, "methodName", func(self, ...))</tt> + * postcondition is in the form of either <tt>postcondition(object, "methodName", func(returnValues, self, ...))</tt> or <tt>postcondition(object, "methodName", func(oldValues, returnValues, self, ...), populateOld(oldValues, self, ...))</tt> + ** returnValues is the list of return values, empty if no return values were sent. + ** if the populateOld function is provided, then the empty oldValues table is provided and expected to be filled, and then given to the func. + * argCheck is in the form of <tt>argCheck(value, n, "type1" [, "type2", ...])</tt> + ** value is the value provided to the function you're checking. + ** n is the argument position. ''Note: 1 is the position of `self'. 2 would be the first "real" position.'' + ** the tuple of types can be any string, but specifically "nil", "boolean", "string", "number", "function", "userdata", "table", etc. + Arguments: + string - the namespace. ''Note: this doesn't necessarily have to be a string.'' + Example: + local precondition, postcondition, argCheck = Rock:GetRecyclingFunctions("Stack", "precondition", "postcondition", "argCheck") + + local stack = {} + stack.IsEmpty = function(self) + return self[1] == nil + end + stack.GetLength = function(self) + return #self + end + stack.Push = function(self, value) + self[#self+1] = value + end + precondition(stack, "Push", function(self, value) + argCheck(value, 2, "string") -- only accept strings, no other values + end) + postcondition(stack, "Push", function(old, ret, self, value) + assert(self:GetLength() == old.length+1) + assert(not self:IsEmpty()) + end, function(old, self) + old.length = self:GetLength() + end) + stack.Pop = function(self) + local value = self[#self] + self[#self] = nil + return value + end + precondition(stack, "Pop", function(self) + assert(self:GetLength() >= 1) + end) + postcondition(stack, "Pop", function(old, ret, self) + assert(self:GetLength() == old.length-1) + end, function(old, self) + old.length = self:GetLength() + end) + stack.Peek = function(self) + return self[#self] + end + precondition(stack, "Peek", function(self) + assert(self:GetLength() >= 1) + end) + postcondition(stack, "Peek", function(old, ret, self) + assert(self:GetLength() == old.length) + end, function(old, self) + old.length = self:GetLength() + end) + + local t = setmetatable({}, {__index=stack}) + t:Push("Alpha") + t:Push("Bravo") + t:Push(5) -- error, only strings + assert(t:Pop() == "Bravo") + assert(t:Pop() == "Alpha") + t:Pop() -- error, out of values + -----------------------------------------------------------------------------]] + function Rock:GetContractFunctions(namespace, ...) + if namespace == "precondition" or namespace == "postcondition" or namespace == "argCheck" then + error(("Bad argument #2 to `GetContractFunctions'. Cannot be %q."):format(namespace), 2) + end + local t = newList() + if enableContracts then + for i = 1, select('#', ...) do + local v = select(i, ...) + if v == "precondition" then + t[i] = precondition + elseif v == "postcondition" then + t[i] = postcondition + elseif v == "argCheck" then + t[i] = argCheck + else + error(("Bad argument #%d to `GetContractFunctions'. Expected %q, %q, or %q, got %q."):format(i+2, "precondition", "postcondition", "argCheck", tostring(v))) + end + end + else + for i = 1, select('#', ...) do + t[i] = noop + end + end + return unpackListAndDel(t) + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * convert a revision string to a number +Arguments: + string - revision string +Returns: + string or number - the string given or the number retrieved from it. +-----------------------------------------------------------------------------]] +local function coerceRevisionToNumber(version) + if type(version) == "string" then + return tonumber(version:match("(%-?%d+)")) or version + else + return version + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * try to enable the standalone library specified +Arguments: + string - name of the library. +Returns: + boolean - whether the library is properly enabled and loadable. +-----------------------------------------------------------------------------]] +local function TryToEnable(addon) + local islod = IsAddOnLoadOnDemand(addon) + if islod 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 + +--[[--------------------------------------------------------------------------- +Notes: + * try to load the standalone library specified +Arguments: + string - name of the library. +Returns: + boolean - whether the library is loaded. +-----------------------------------------------------------------------------]] +local function TryToLoadStandalone(major) + major = major:lower() + if scannedLibraries[major] then + return + end + scannedLibraries[major] = true + local name, _, _, enabled, loadable, state = GetAddOnInfo(major) + if state == "MISSING" or not IsAddOnLoadOnDemand(major) then + -- backwards compatibility for X-AceLibrary + local field = "X-AceLibrary-" .. major + local loaded + 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 + elseif (enabled and loadable) or TryToEnable(major) then + LoadAddOn(major) + return true + else + return false + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * Return the LibStub library, casing is unimportant. +Arguments: + string - name of the library. +Returns: + table or nil - library + number - minor version +-----------------------------------------------------------------------------]] +local function GetLibStubLibrary(major) + local lib, minor = LibStub:GetLibrary(major, true) + if lib then + return lib, minor + end + major = major:lower() + for m, lib in LibStub:IterateLibraries() do + if m:lower() == major then + return LibStub:GetLibrary(m) + end + end + return nil, nil +end + +local finishLibraryRegistration +--[[--------------------------------------------------------------------------- +Notes: + * create a new library if the version provided is not out of date. +Arguments: + string - name of the library. + number - version of the library. +Returns: + library, oldLibrary + * table or nil - the library with which to manipulate + * table or nil - the old version of the library to upgrade from +Example: + local LibMonkey, oldLib = Rock:NewLibrary("LibMonkey-1.0", 50) + if not LibMonkey then + -- opt out now, out of date + return + end +-----------------------------------------------------------------------------]] +function Rock:NewLibrary(major, version) + if type(major) ~= "string" then + error(("Bad argument #2 to `NewLibrary'. Expected %q, got %q."):format("string", type(major)), 2) + end + if not major:match("^Lib[A-Z][A-Za-z%d%-]*%-%d+%.%d+$") then + error(("Bad argument #2 to `NewLibrary'. Must match %q, got %q."):format("^Lib[A-Z][A-Za-z%d%-]*%-%d+%.%d+$", major), 2) + end + TryToLoadStandalone(major) + version = coerceRevisionToNumber(version) + if type(version) ~= "number" then + error(("Bad argument #3 to `NewLibrary'. Expected %q, got %q."):format("number", type(version)), 2) + end + local library, oldMinor = LibStub:GetLibrary(major, true) + if oldMinor and oldMinor >= version then + -- in case LibStub is acting funny + return nil, nil + end + local library, oldMinor = LibStub:NewLibrary(major, version) + if not library then + return nil, nil + end + local unitTests_major = unitTests[major] + if unitTests_major then + for k,v in pairs(unitTests_major) do + unitTests_major[k] = nil + end + end + for k, v in pairs(recycleData) do + v[major] = nil + end + local mixinToObject_library = mixinToObject[library] + + local oldLib + if oldMinor then + -- previous version exists + local mixins = newList() + for mixin, objectSet in pairs(mixinToObject) do + if objectSet[library] then + mixins[mixin] = true + end + end + for mixin in pairs(mixins) do + mixin:Unembed(library) + end + mixins = del(mixins) + oldLib = newList() + for k, v in pairs(library) do + oldLib[k] = v + library[k] = nil + end + setmetatable(oldLib, getmetatable(library)) + setmetatable(library, nil) + end + finishLibraryRegistration(major, version, library, figureCurrentAddon(2)) + + return library, oldLib +end +function finishLibraryRegistration(major, version, library, folder) + library.name = major + + libraries[major] = library + pendingLibraries[library] = folder + local exportedMethods_library = exportedMethods[library] + if exportedMethods_library then + local mixinToObject_library = mixinToObject[library] + if mixinToObject_library then + for object in pairs(mixinToObject_library) do + for _,v in ipairs(exportedMethods_library) do + object[v] = nil + end + end + end + exportedMethods[library] = del(exportedMethods_library) + end + if library ~= Rock then + Rock:Embed(library) + end + + frame:Show() +end +if not oldRock then + finishLibraryRegistration(MAJOR_VERSION, MINOR_VERSION, Rock, figureCurrentAddon(1)) +end + +-- #NODOC +local function __removeLibrary(libName) + libraries[libName] = nil + if LibStub.libs then + LibStub.libs[libName] = nil + end + if LibStub.minors then + LibStub.minors[libName] = nil + end + local lastCount + repeat + lastCount = collectgarbage('count') + collectgarbage('collect') + until lastCount == collectgarbage('count') +end +local function run(_,a) + if a < 1/30 then + collectgarbage('step') + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * properly finalizes the library, essentially stating that it has loaded properly. + * This will call :OnLibraryLoad("major", library) on every other library + * This will also call :OnLibraryLoad("major", library) on the library provided, using every other library as the arguments. + * An error will occur if this is not done before ADDON_LOADED. +Arguments: + string - name of the library. +Example: + local LibMonkey, oldLib = Rock:NewLibrary("LibMonkey-1.0", 50) + if not LibMonkey then + -- opt out now, out of date + return + end + Rock:FinalizeLibrary("LibMonkey-1.0") +-----------------------------------------------------------------------------]] +function Rock:FinalizeLibrary(major) + if type(major) ~= "string" then + error(("Bad argument #2 to `FinalizeLibrary'. Expected %q, got %q."):format("string", type(major)), 2) + end + local library = libraries[major] + if not library then + error(("Bad argument #2 to `FinalizeLibrary'. %q is not a library."):format("string", major), 2) + end + pendingLibraries[library] = nil + local library_OnLibraryLoad = library.OnLibraryLoad + if library_OnLibraryLoad then + for maj, lib in LibStub:IterateLibraries() do -- for all libraries + if maj ~= major then + local success, ret = pcall(library_OnLibraryLoad, library, maj, lib) + if not success then + geterrorhandler()(ret) + break + end + end + end + end + if finalizedLibraries[library] then + return + end + finalizedLibraries[library] = true + for maj, lib in pairs(libraries) do -- just Rock libraries + if maj ~= major then + local lib_OnLibraryLoad = lib.OnLibraryLoad + if lib_OnLibraryLoad then + local success, ret = pcall(lib_OnLibraryLoad, lib, major, library) + if not success then + geterrorhandler()(ret) + end + end + end + end + if LibRockEvent then + self:DispatchEvent("LibraryLoad", major, library) + end +end + +local function manualFinalize(major, library) + if libraries[major] then -- non-Rock libraries only + return + end + if finalizedLibraries[library] then -- don't do it twice + return + end + finalizedLibraries[library] = true + for maj, lib in pairs(libraries) do -- just Rock libraries + if maj ~= major then + local lib_OnLibraryLoad = lib.OnLibraryLoad + if lib_OnLibraryLoad then + local success, ret = pcall(lib_OnLibraryLoad, lib, major, library) + if not success then + geterrorhandler()(ret) + end + end + end + end + if LibRockEvent then + Rock:DispatchEvent("LibraryLoad", major, library) + end +end + +--[[--------------------------------------------------------------------------- +Arguments: + string - name of the library. + [optional] boolean - whether to not load a library if it is not found. Default: false + [optional] boolean - whether to not error if a library is not found. Default: false +Returns: + library + * table or nil - the library requested +Example: + local LibMonkey = Rock:GetLibrary("LibMonkey-1.0") + -- or + local LibMonkey = Rock("LibMonkey-1.0") +-----------------------------------------------------------------------------]] +function Rock:GetLibrary(major, dontLoad, dontError) + if type(major) ~= "string" then + error(("Bad argument #2 to `GetLibrary'. Expected %q, got %q."):format("string", type(major)), 2) + end + if dontLoad and dontLoad ~= true then + error(("Bad argument #3 to `GetLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontLoad)), 2) + end + if dontError and dontError ~= true then + error(("Bad argument #4 to `GetLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontError)), 2) + end + if not dontLoad then + TryToLoadStandalone(major) + end + + local library = GetLibStubLibrary(major) + if not library then + if dontError then + return nil + end + error(("Library %q not found."):format(major), 2) + end + + return library +end + +setmetatable(Rock, { __call = Rock.GetLibrary }) + +--[[--------------------------------------------------------------------------- +Arguments: + string - name of the library. +Returns: + boolean - whether the library exists and is a proper mixin which can be embedded. +Example: + local isMixin = Rock:IsLibraryMixin("LibMonkey-1.0") +-----------------------------------------------------------------------------]] +function Rock:IsLibraryMixin(name) + local library = self:GetLibrary(name, false, true) + if not library then + return false + end + return not not exportedMethods[library] +end + +--[[--------------------------------------------------------------------------- +Arguments: + string - name of the library. + [optional] boolean - whether to not load a library if it is not found. Default: false +Returns: + library + * table or nil - the library requested +Example: + local hasLibMonkey = Rock:HasLibrary("LibMonkey-1.0") +-----------------------------------------------------------------------------]] +function Rock:HasLibrary(major, dontLoad) + if type(major) ~= "string" then + error(("Bad argument #2 to `HasLibrary'. Expected %q, got %q."):format("string", type(major)), 2) + end + if dontLoad and dontLoad ~= true then + error(("Bad argument #3 to `HasLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontLoad)), 2) + end + if not dontLoad then + TryToLoadStandalone(major) + end + return not not GetLibStubLibrary(major) +end + +--[[--------------------------------------------------------------------------- +Notes: + * This is exported to all libraries +Returns: + major, minor + * string - name of the library + * number - version of the library +Example: + local major, minor = Rock:GetLibraryVersion() -- will be "LibRock-1.0", 12345 + local major, minor = LibMonkey:GetLibraryVersion() -- will be "LibMonkey-1.0", 50 +-----------------------------------------------------------------------------]] +function Rock:GetLibraryVersion() + if type(self) ~= "table" then + return nil, nil + end + local major + local name = self.name + if name and GetLibStubLibrary(name) == self then + major = name + else + for m, instance in LibStub:IterateLibraries() do + if instance == self then + major = m + break + end + end + if not major then + return nil, nil + end + end + local _, minor = GetLibStubLibrary(major) + return major, minor +end + +--[[--------------------------------------------------------------------------- +Returns: + an iterator to traverse all registered libraries. +Example: + for major, library in Rock:IterateLibraries() do + -- do something with major and library + end +-----------------------------------------------------------------------------]] +function Rock:IterateLibraries() + return LibStub:IterateLibraries() +end + +--[[--------------------------------------------------------------------------- +Notes: + * This is exported to all libraries + * Allows you to set precisely what methods for the library to export. + * This automatically turns a library into a mixin. +Arguments: + tuple - the list of method names to export. +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50) + LibMonkey.FlingPoo = function(self) + return "Splat!" + end + LibMonkey:SetExportedMethods("FlingPoo") + -- later + local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0") + assert(Darwin:FlingPoo() == "Splat!") +-----------------------------------------------------------------------------]] +function Rock:SetExportedMethods(...) + if exportedMethods[self] then + error("Cannot call `SetExportedMethods' more than once.", 2) + end + local t = newList(...) + if #t == 0 then + error("Must supply at least 1 method to `SetExportedMethods'.", 2) + end + for i,v in ipairs(t) do + if type(self[v]) ~= "function" then + error(("Bad argument #%d to `SetExportedMethods'. Method %q does not exist."):format(i+1, tostring(v)), 2) + end + end + exportedMethods[self] = t + + local mixinToObject_library = mixinToObject[self] + if mixinToObject_library then + for object in pairs(mixinToObject_library) do + for _,method in ipairs(t) do + object[method] = self[method] + end + end + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * This is exported to all libraries + * Embeds all the methods previously set to export onto a table. + * This will call :OnEmbed(object) on the library if it is available. +Arguments: + table - the table with which to export methods onto. +Returns: + The table provided, after embedding. +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50) + LibMonkey.FlingPoo = function(self) + return "Splat!" + end + LibMonkey:SetExportedMethods("FlingPoo") + -- later + local Darwin = {} + Rock("LibMonkey-1.0"):Embed(Darwin) + assert(Darwin:FlingPoo() == "Splat!") +-----------------------------------------------------------------------------]] +function Rock:Embed(object) + if not exportedMethods[self] then + error(("Cannot call `Embed' for library %q if `SetExportedMethods' has not been called."):format(tostring(self.name)), 2) + end + if type(object) ~= "table" then + error(("Bad argument #2 to `Embed'. Expected %q, got %q."):format("table", type(object)), 2) + end + + for i,v in ipairs(exportedMethods[self]) do + if type(self[v]) ~= "function" then + error(("Problem embedding method %q from library %q. Expected %q, got %q."):format(tostring(v), better_tostring(self), "function", type(self[v]))) + end + object[v] = self[v] + end + + if not mixinToObject[self] then + -- weak because objects come and go + mixinToObject[self] = setmetatable(newList(), weakKey) + end + if mixinToObject[self][object] then + error(("Cannot embed library %q into the same object %q more than once."):format(better_tostring(self), better_tostring(object)), 2) + end + mixinToObject[self][object] = true + if type(rawget(object, 'mixins')) == "table" then + object.mixins[self] = true + end + + local self_OnEmbed = self.OnEmbed + if self_OnEmbed then + local success, ret = pcall(self_OnEmbed, self, object) + if not success then + geterrorhandler()(ret) + end + end + + return object +end + +--[[--------------------------------------------------------------------------- +Notes: + * This is exported to all libraries + * Unembeds all the methods previously set to export onto a table. + * This will error if the library is not embedded on the object + * This will call :OnUnembed(object) on the library if it is available. +Arguments: + table - the table with which to export methods onto. +Returns: + The table provided, after embedding. +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50) + LibMonkey.FlingPoo = function(self) + return "Splat!" + end + LibMonkey:SetExportedMethods("FlingPoo") + -- later + local Darwin = {} + Rock("LibMonkey-1.0"):Embed(Darwin) + assert(Darwin:FlingPoo() == "Splat!") + Rock("LibMonkey-1.0"):Unembed(Darwin) + assert(Darwin.FlingPoo == nil) +-----------------------------------------------------------------------------]] +function Rock:Unembed(object) + if not exportedMethods[self] then + error(("Cannot call `Unembed' for library %q if `SetExportedMethods' has not been called."):format(better_tostring(self)), 2) + end + + if not mixinToObject[self] or not mixinToObject[self][object] then + error(("Cannot unembed library %q from object %q, since it is not embedded originally."):format(better_tostring(self), better_tostring(object)), 2) + end + local mixinToObject_self = mixinToObject[self] + mixinToObject_self[object] = nil + if not next(mixinToObject_self) then + mixinToObject[self] = del(mixinToObject_self) + end + + local mixin_OnUnembed = self.OnUnembed + if mixin_OnUnembed then + local success, ret = pcall(mixin_OnUnembed, self, object) + if not success then + geterrorhandler()(ret) + end + end + + for i,v in ipairs(exportedMethods[self]) do + object[v] = nil + end +end + +local function embedAce2Mixin(mixin, object) + if not mixinToObject[mixin] then + mixinToObject[mixin] = setmetatable(newList(), weakKey) + end + mixinToObject[mixin][object] = true + mixin:embed(object) +end + +local function embedLibStubMixin(mixin, object) + if not mixinToObject[mixin] then + mixinToObject[mixin] = setmetatable(newList(), weakKey) + end + mixinToObject[mixin][object] = true + mixin:Embed(object) +end + +--[[--------------------------------------------------------------------------- +Notes: + * create a new addon with the specified name. +Arguments: + string - name of the addon. + tuple - list of mixins with which to embed into this addon. +Returns: + addon + * table - the addon with which to manipulate +Example: + local MyAddon = Rock:NewAddon("MyAddon", "Mixin-1.0", "OtherMixin-2.0") +-----------------------------------------------------------------------------]] +function Rock:NewAddon(name, ...) + if type(name) ~= "string" then + error(("Bad argument #2 to `NewAddon'. Expected %q, got %q"):format("string", type(name)), 2) + end + if name:match("^Lib[A-Z]") then + error(("Bad argument #2 to `NewAddon'. Cannot start with %q, got %q."):format("Lib", name), 2) + end + if self == Rock and name:match("_") then + error(("Bad argument #2 to `NewAddon'. Cannot contain underscores, got %q."):format(name), 2) + end + + if addons[name] then + error(("Bad argument #2 to `NewAddon'. Addon %q already created."):format(name), 2) + end + local addon = setmetatable(newList(), addon_mt) + addon.name = name + + local mixinSet = newList() + + for i = 1, select('#', ...) do + local libName = select(i, ...) + if mixinSet[libName] then + error(("Bad argument #%d to `NewAddon'. %q already stated."):format(i+2, tostring(libName)), 2) + end + mixinSet[libName] = true + TryToLoadStandalone(libName) + local library = Rock:GetLibrary(libName, false, true) + if not library then + error(("Bad argument #%d to `NewAddon'. Library %q is not found."):format(i+2, tostring(libName)), 2) + end + + local style = 'rock' + + if not exportedMethods[library] then + local good = false + if AceLibrary then + local AceOO = AceLibrary:HasInstance("AceOO-2.0", false) and AceLibrary("AceOO-2.0") + if AceOO.inherits(library, AceOO.Mixin) then + good = true + style = 'ace2' + end + end + if not good and type(rawget(library, 'Embed')) == "function" then + good = true + style = 'libstub' + end + if not good then + error(("Bad argument #%d to `NewAddon'. Library %q is not a mixin."):format(i+2, tostring(libName)), 2) + end + end + + if library == Rock then + error(("Bad argument #%d to `NewAddon'. Cannot use %q as a mixin."):format(i+2, tostring(libName)), 2) + end + + if style == 'rock' then + library:Embed(addon) + elseif style == 'ace2' then + embedAce2Mixin(library, addon) + elseif style == 'libstub' then + embedLibStubMixin(library, addon) + end + end + + mixinSet = del(mixinSet) + + addons[name] = addon + pendingAddons[#pendingAddons+1] = addon + pendingAddonsEnable[#pendingAddonsEnable+1] = addon + addonToFolder[addon] = figureCurrentAddon(self == Rock and 2 or 4) + + frame:Show() + + return addon +end + +--[[--------------------------------------------------------------------------- +Arguments: + string - name of the addon. +Returns: + addon + * table or nil - the addon requested +Example: + local MyAddon = Rock:GetAddon("MyAddon") +-----------------------------------------------------------------------------]] +function Rock:GetAddon(name) + if type(name) ~= "string" then + return nil + end + local addon = addons[name] + if addon then + return addon + end + name = name:lower() + for k, v in pairs(addons) do + if k:lower() == name then + return v + end + end + return nil +end + +--[[--------------------------------------------------------------------------- +Arguments: + string or table - name of the addon or the addon itself. +Returns: + boolean - whether the addon requested exists. +Example: + local hasMyAddon = Rock:HasAddon("MyAddon") + -- or + local hasMyAddon = Rock:HasAddon(MyAddon) +-----------------------------------------------------------------------------]] +function Rock:HasAddon(name) + if type(name) == "string" then + local addon = addons[name] + if addon then + return true + end + name = name:lower() + for k, v in pairs(addons) do + if k:lower() == name then + return true + end + end + elseif type(name) == "table" then + for k,v in pairs(addons) do + if v == name then + return true + end + end + end + return false +end + +--[[--------------------------------------------------------------------------- +Returns: + an iterator to traverse all addons created with Rock. +Example: + for name, addon in Rock:IterateAddons() do + -- do something with name and addon + end +-----------------------------------------------------------------------------]] +function Rock:IterateAddons() + return pairs(addons) +end + +--[[--------------------------------------------------------------------------- +Arguments: + string - major version of the mixin library +Returns: + an iterator to traverse all objects that the given mixin has embedded into +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0") + local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0") + for object in LibMonkey:IterateMixinObjects("LibMonkey-1.0") do + assert(object == Darwin) + end +-----------------------------------------------------------------------------]] +function Rock:IterateMixinObjects(mixinName) + local mixin + if type(mixinName) == "table" then + mixin = mixinName + else + if type(mixinName) ~= "string" then + error(("Bad argument #2 to `IterateMixinObjects'. Expected %q or %q, got %q."):format("table", "string", type(mixinName)), 2) + end + mixin = libraries[mixinName] + end + local mixinToObject_mixin = mixinToObject[mixin] + if not mixinToObject_mixin then + return noop + end + return pairs(mixinToObject_mixin) +end + +local function iter(object, mixin) + mixin = next(mixinToObject, mixin) + if not mixin then + return nil + elseif mixinToObject[mixin][object] then + return mixin + end + return iter(object, mixin) -- try next mixin +end +--[[--------------------------------------------------------------------------- +Returns: + an iterator to traverse all mixins that an object has embedded +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0") + local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0") + for mixin in Rock:IterateObjectMixins(Darwin) do + assert(mixin == LibMonkey) + end +-----------------------------------------------------------------------------]] +function Rock:IterateObjectMixins(object) + if type(object) ~= "table" then + error(("Bad argument #2 to `IterateObjectMixins'. Expected %q, got %q."):format("table", type(object)), 2) + end + return iter, object, nil +end + +--[[--------------------------------------------------------------------------- +Arguments: + table - the object to check + string - the mixin to check +Returns: + boolean - whether the object has the given mixin embedded into it. +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0") + local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0") + assert(Rock:DoesObjectUseMixin(Darwin, "LibMonkey-1.0")) +-----------------------------------------------------------------------------]] +function Rock:DoesObjectUseMixin(object, mixinName) + if type(object) ~= "table" then + error(("Bad argument #2 to `IterateObjectMixins'. Expected %q, got %q."):format("table", type(object)), 2) + end + local mixin + if type(mixinName) == "table" then + mixin = mixinName + else + if type(mixinName) ~= "string" then + error(("Bad argument #3 to `IterateMiDoesObjectUseMixininObjects'. Expected %q or %q, got %q."):format("table", "string", type(mixinName)), 2) + end + mixin = libraries[mixinName] + end + if not mixin then + return false + end + + local mixinToObject_mixin = mixinToObject[mixin] + if not mixinToObject_mixin then + return false + end + return not not mixinToObject_mixin[object] +end + +Rock.UID_NUM = oldRock and oldRock.UID_NUM or 0 +--[[--------------------------------------------------------------------------- +Notes: + * This UID is not unique across sessions. If you save a UID in a saved variable, the same UID can be generated in another session. +Returns: + number - a unique number. +Example: + local UID = Rock:GetUID() +-----------------------------------------------------------------------------]] +function Rock:GetUID() + local num = Rock.UID_NUM + 1 + Rock.UID_NUM = num + return num +end + +local function unobfuscateEmail(email) + return email:gsub(" AT ", "@"):gsub(" DOT ", ".") +end +local function fix(char) + return ("%%%02x"):format(char:byte()) +end +local function urlencode(text) + return text:gsub("[^0-9A-Za-z]", fix) +end + +local url +local function makeURLFrame() + makeURLFrame = nil + local function bumpFrameLevels(frame, amount) + frame:SetFrameLevel(frame:GetFrameLevel()+amount) + local children = newList(frame:GetChildren()) + for _,v in ipairs(children) do + bumpFrameLevels(v, amount) + end + children = del(children) + end + -- some code borrowed from Prat here + StaticPopupDialogs["ROCK_SHOW_URL"] = { + text = not IsMacClient() and L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] or L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."], + button2 = ACCEPT, + hasEditBox = 1, + hasWideEditBox = 1, + showAlert = 1, -- HACK : it's the only way I found to make de StaticPopup have sufficient width to show WideEditBox :( + + OnShow = function() + local editBox = _G[this:GetName() .. "WideEditBox"] + editBox:SetText(url) + editBox:SetFocus() + editBox:HighlightText(0) + editBox:SetScript("OnTextChanged", function() StaticPopup_EditBoxOnTextChanged() end) + + local button = _G[this:GetName() .. "Button2"] + button:ClearAllPoints() + button:SetWidth(200) + button:SetPoint("CENTER", editBox, "CENTER", 0, -30) + + _G[this:GetName() .. "AlertIcon"]:Hide() -- HACK : we hide the false AlertIcon + this:SetFrameStrata("FULLSCREEN_DIALOG") + bumpFrameLevels(this, 30) + end, + OnHide = function() + local editBox = _G[this:GetName() .. "WideEditBox"] + editBox:SetScript("OnTextChanged", nil) + this:SetFrameStrata("DIALOG") + bumpFrameLevels(this, -30) + end, + OnAccept = function() end, + OnCancel = function() end, + EditBoxOnEscapePressed = function() this:GetParent():Hide() end, + EditBoxOnTextChanged = function() + this:SetText(url) + this:SetFocus() + this:HighlightText(0) + end, + timeout = 0, + whileDead = 1, + hideOnEscape = 1 + } +end + +function OpenDonationFrame(self) + if makeURLFrame then + makeURLFrame() + end + + local donate = self.donate + if type(donate) ~= "string" then + donate = "Wowace" + end + local style, data = (":"):split(donate, 2) + style = style:lower() + if style ~= "website" and style ~= "paypal" then + style = "wowace" + end + if style == "wowace" then + url = "http://www.wowace.com/wiki/Donations" + elseif style == "website" then + url = data + else -- PayPal + local text = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=" .. urlencode(unobfuscateEmail(data)) + local name + if type(self.title) == "string" then + name = self.title + elseif type(self.name) == "string" then + name = self.name + end + if name == MAJOR_VERSION then + name = "Rock" + end + if name then + name = name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "") + text = text .. "&item_name=" .. urlencode(name) + end + url = text + end + + StaticPopup_Show("ROCK_SHOW_URL") +end +function OpenIssueFrame(self) + if makeURLFrame then + makeURLFrame() + end + + local issueTracker = self.issueTracker + if type(issueTracker) ~= "string" then + return + end + local style, data = (":"):split(issueTracker, 2) + style = style:lower() + if style ~= "website" and style ~= "wowace" then + return + end + if style == "wowace" then + url = "http://jira.wowace.com/secure/CreateIssue.jspa?pid=" .. data + elseif style == "website" then + url = data + end + + StaticPopup_Show("ROCK_SHOW_URL") +end +local function donate_hidden(addon) + return type(addon.donate) ~= "string" +end + +local function issue_hidden(addon) + return type(addon.issueTracker) ~= "string" +end + +-- #NODOC +function Rock:GetRockConfigOptions(addon) + return 'active', { + type = 'boolean', + name = L["Enabled"], + desc = L["Enable or disable this addon."], + get = 'IsActive', + set = 'ToggleActive', + handler = addon, + order = -1, + }, 'donate', { + type = 'execute', + name = L["Give donation"], + buttonText = L["Donate"], + desc = L["Give a much-needed donation to the author of this addon."], + func = OpenDonationFrame, + hidden = donate_hidden, + passValue = addon, + order = -2, + }, 'issue', { + type = 'execute', + name = L["File issue"], + buttonText = L["Report"], + desc = L["File a bug or request a new feature or an improvement to this addon."], + func = OpenIssueFrame, + hidden = issue_hidden, + passValue = addon, + order = -3, + } +end + +local function initAddon(addon, name) + name = addonToFolder[addon] or name or "" + -- TOC checks + if addon.title == nil then + addon.title = GetAddOnMetadata(name, "Title") + end + if type(addon.title) == "string" then + addon.title = addon.title:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):gsub("%-Rock%-$", ""):trim() + end + if addon.notes == nil then + addon.notes = GetAddOnMetadata(name, "Notes") + end + if type(addon.notes) == "string" then + addon.notes = addon.notes:trim() + end + if addon.version == nil then + addon.version = GetAddOnMetadata(name, "Version") + end + if type(addon.version) == "string" then + addon.version = addon.version:trim() + end + if addon.author == nil then + addon.author = GetAddOnMetadata(name, "Author") + end + if type(addon.author) == "string" then + addon.author = addon.author:trim() + end + if addon.credits == nil then + addon.credits = GetAddOnMetadata(name, "X-Credits") + end + if type(addon.credits) == "string" then + addon.credits = addon.credits:trim() + end + if addon.donate == nil then + addon.donate = GetAddOnMetadata(name, "X-Donate") + end + if type(addon.donate) == "string" then + addon.donate = addon.donate:trim() + end + if addon.issueTracker == nil then + addon.issueTracker = GetAddOnMetadata(name, "X-IssueTracker") + end + if type(addon.issueTracker) == "string" then + addon.issueTracker = addon.issueTracker:trim() + end + if addon.category == nil then + addon.category = GetAddOnMetadata(name, "X-Category") + end + if type(addon.category) == "string" then + addon.category = addon.category:trim() + end + if addon.email == nil then + addon.email = GetAddOnMetadata(name, "X-eMail") or GetAddOnMetadata(name, "X-Email") + end + if type(addon.email) == "string" then + addon.email = addon.email:trim() + end + if addon.license == nil then + addon.license = GetAddOnMetadata(name, "X-License") + end + if type(addon.license) == "string" then + addon.license = addon.license:trim() + end + if addon.website == nil then + addon.website = GetAddOnMetadata(name, "X-Website") + end + if type(addon.website) == "string" then + addon.website = addon.website:trim() + end + + for mixin in Rock:IterateObjectMixins(addon) do + local mixin_OnEmbedInitialize = mixin.OnEmbedInitialize + if mixin_OnEmbedInitialize then + local success, ret = pcall(mixin_OnEmbedInitialize, mixin, addon) + if not success then + geterrorhandler()(ret) + end + end + end + + local addon_OnInitialize = addon.OnInitialize + if addon_OnInitialize then + local success, ret = pcall(addon_OnInitialize, addon) + if not success then + geterrorhandler()(ret) + end + end + + if LibRockEvent then + Rock:DispatchEvent("AddonInitialized", addon) + end +end + + +local function manualEnable(addon) + for i,v in ipairs(pendingAddons) do + if v == addon then + return + end + end + if currentlyEnabledAddons[addon] then + return false + end + currentlyEnabledAddons[addon] = true + + local first = not addonsAlreadyEnabled[addon] + addonsAlreadyEnabled[addon] = true + + for mixin in Rock:IterateObjectMixins(addon) do + local mixin_OnEmbedEnable = mixin.OnEmbedEnable + if mixin_OnEmbedEnable then + local success, ret = pcall(mixin_OnEmbedEnable, mixin, addon, first) + if not success then + geterrorhandler()(ret) + end + end + end + local addon_OnEnable = addon.OnEnable + if addon_OnEnable then + local success, ret = pcall(addon_OnEnable, addon, first) + if not success then + geterrorhandler()(ret) + end + end + + if LibRockEvent then + Rock:DispatchEvent("AddonEnabled", addon, first) + end + + return true, first +end + +local function manualDisable(addon) + if not currentlyEnabledAddons[addon] then + return false + end + currentlyEnabledAddons[addon] = nil + + for mixin in Rock:IterateObjectMixins(addon) do + local mixin_OnEmbedDisable = mixin.OnEmbedDisable + if mixin_OnEmbedDisable then + local success, ret = pcall(mixin_OnEmbedDisable, mixin, addon) + if not success then + geterrorhandler()(ret) + end + end + end + local addon_OnDisable = addon.OnDisable + if addon_OnDisable then + local success, ret = pcall(addon_OnDisable, addon) + if not success then + geterrorhandler()(ret) + end + end + + if LibRockEvent then + Rock:DispatchEvent("AddonDisabled", addon) + end + return true +end + +local function enableAddon(addon) + for i,v in ipairs(pendingAddons) do + if v == addon then + return + end + end + if addon_mt___index.IsActive(addon) then + manualEnable(addon) + end +end + +-- #NODOC +-- This is used by internal Rock libraries after updating the active state. +function Rock:RecheckEnabledStates() + local changed = false + for _,addon in pairs(addons) do + local good = true + for _,a in ipairs(pendingAddonsEnable) do + if addon == a then + good = false + break + end + end + if good then + if addon_mt___index.IsActive(addon) then + if manualEnable(addon) then + changed = true + end + else + if manualDisable(addon) then + changed = true + end + end + end + end + if changed then + return self:RecheckEnabledStates() + end +end + +frame:UnregisterAllEvents() +frame:RegisterEvent("ADDON_LOADED") +frame:RegisterEvent("PLAYER_LOGIN") +local function runMainAddonLoadedChunk(name) + local tmp = newList() + tmp, pendingAddons = pendingAddons, tmp + for i, addon in ipairs(tmp) do + local folder = addonToFolder[addon] + if name and folder and not foldersLoaded[folder] then + for j = i, #tmp do + pendingAddons[#pendingAddons+1] = tmp[j] + tmp[j] = nil + end + break + end + initAddon(addon, name) + end + + if IsLoggedIn() then + for i, addon in ipairs(tmp) do + for j, v in ipairs(pendingAddonsEnable) do + if v == addon then + table_remove(pendingAddonsEnable, i) + break + end + end + enableAddon(addon) + end + for i, addon in ipairs(pendingAddonsEnable) do + local good = true + for j, v in ipairs(pendingAddons) do + if v == addon then + good = false + break + end + end + if not good then + break + end + pendingAddonsEnable[i] = nil + enableAddon(addon) + end + end + tmp = del(tmp) + for library, addonName in pairs(pendingLibraries) do + if not name or foldersLoaded[addonName] then + local success, ret = pcall(error, ("Library %q not finalized before ADDON_LOADED."):format(better_tostring(library)), 3) + geterrorhandler()(ret) + Rock:FinalizeLibrary((library:GetLibraryVersion())) + end + end + + if isStandalone then + local LibRock_1_0DB = _G.LibRock_1_0DB + if type(LibRock_1_0DB) ~= "table" then + LibRock_1_0DB = {} + _G.LibRock_1_0DB = LibRock_1_0DB + end + if type(LibRock_1_0DB.unitTests) ~= "table" then + LibRock_1_0DB.unitTests = {} + end + enableContracts = LibRock_1_0DB.contracts or false + unitTestDB = LibRock_1_0DB.unitTests + for namespace, data in pairs(unitTests) do + if not unitTestDB[namespace] then + if data then + del(data) + unitTests[namespace] = false + end + elseif data and (not name or data.addon == name) then + local stats = newList() + for i,v in ipairs(data) do + data[i] = nil + + local libs = newList() + for k,v in pairs(libraries) do + libs[k] = v + end + + local success, ret = pcall(v) + if not success then + geterrorhandler()(ret) + stats[i] = ret + else + stats[i] = false + end + + for k in pairs(libraries) do + if not libs[k] then + __removeLibrary(k) + end + end + libs = del(libs) + + local lastCount + repeat + lastCount = collectgarbage('count') + collectgarbage('collect') + until lastCount == collectgarbage('count') + end + del(data) + unitTests[namespace] = false + if #stats >= 1 then + local pass, fail = 0, 0 + for i,v in ipairs(stats) do + if v then + fail = fail + 1 + else + pass = pass + 1 + end + end + + local color + if fail == 0 then + _G.DEFAULT_CHAT_FRAME:AddMessage(("|cff00ff00%s: %d unit test(s) passed."):format(namespace, pass)) + elseif pass > 0 then + _G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s: %d unit test(s) passed, %d unit test(s) failed."):format(namespace, pass, fail)) + else + _G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s: %d unit test(s) failed."):format(namespace, fail)) + end + for i,v in ipairs(stats) do + if v then + _G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s|r"):format(tostring(v))) + end + end + if fail > 0 then + _G.DEFAULT_CHAT_FRAME:AddMessage("|cffff0000----------|r") + end + end + stats = del(stats) + end + end + end + if isStandalone and name == MAJOR_VERSION then + Rock("LibRockEvent-1.0", false, true) -- load if possible + Rock("LibRockConsole-1.0", false, true) -- load if possible - I like the default chat commands + Rock("LibRockComm-1.0", false, true) -- load if possible - has version checking and the like + Rock("LibRockConfig-1.0", false, true) -- load if possible - LibRock-1.0 registers with it. + end + + for major, library in LibStub:IterateLibraries() do + manualFinalize(major, library) + end + + if IsLoggedIn() then + collectgarbage('collect') + end +end + +frame:Show() +frame:SetScript("OnUpdate", function(this, elapsed) + -- capture all un-initialized addons. + runMainAddonLoadedChunk() + collectgarbage('collect') + this:SetScript("OnUpdate", run) +end) +frame:SetScript("OnEvent", function(this, event, ...) + if event == "ADDON_LOADED" then + -- this creates a new table and flushes the old in case someone LoDs an addon inside ADDON_LOADED. + local name = ... + foldersLoaded[name] = true + runMainAddonLoadedChunk(name) + frame:Show() + elseif event == "PLAYER_LOGIN" then + for i, addon in ipairs(pendingAddonsEnable) do + local good = true + for _, a in ipairs(pendingAddons) do + if a == addon then + good = false + break + end + end + if good then + pendingAddonsEnable[i] = nil + enableAddon(addon) + end + end + collectgarbage('collect') + end +end) + +Rock:SetExportedMethods("SetExportedMethods", "Embed", "Unembed", "GetLibraryVersion") + +Rock:FinalizeLibrary(MAJOR_VERSION) + +for major, library in LibStub:IterateLibraries() do + manualFinalize(major, library) +end + +Rock:AddUnitTest(MAJOR_VERSION, function() + -- test recycling + local newList, newDict, newSet, del = Rock:GetRecyclingFunctions(MAJOR_VERSION .. "_UnitTest", "newList", "newDict", "newSet", "del", "Debug") + local t = newList("Alpha", "Bravo", "Charlie") + assert(t[1] == "Alpha") + assert(t[2] == "Bravo") + assert(t[3] == "Charlie") + t = del(t) + t = newList("Alpha", "Bravo", "Charlie") + -- check recycled table + assert(t[1] == "Alpha") + assert(t[2] == "Bravo") + assert(t[3] == "Charlie") + t = del(t) + t = newDict("Alpha", "Bravo", "Charlie", "Delta") + assert(t.Alpha == "Bravo") + assert(t.Charlie == "Delta") + t = del(t) + t = newSet("Alpha", "Bravo", "Charlie") + assert(t.Alpha) + assert(t.Bravo) + assert(t.Charlie) + t = del(t) + + local debug = recycleData.debugPools[MAJOR_VERSION .. "_UnitTest"] + assert(debug.num == 0) + t = newList() + assert(debug.num == 1) + t[1] = newList() + assert(debug.num == 2) + t[2] = newList() + assert(debug.num == 3) + t[1] = del(t[1]) + assert(debug.num == 2) + t[2] = del(t[2]) + assert(debug.num == 1) + t = del(t) + assert(debug.num == 0) +end) + +Rock:AddUnitTest(MAJOR_VERSION, function() + -- test :GetUID() + local t = {} + for i = 1, 10000 do + local uid = Rock:GetUID() + if t[i] then + error(("UID match for iteration %d, UID %s"):format(i, uid)) + end + t[i] = true + end +end) + +Rock:AddUnitTest(MAJOR_VERSION, function() + -- test basic creation and deletion + assert(not LibStub:GetLibrary("LibRockFakeLib-1.0", true)) + assert(not Rock:HasLibrary("LibRockFakeLib-1.0")) + local lib = Rock:NewLibrary("LibRockFakeLib-1.0", 1) + Rock:FinalizeLibrary("LibRockFakeLib-1.0") + lib = nil + assert(LibStub:GetLibrary("LibRockFakeLib-1.0", true)) + assert(Rock:HasLibrary("LibRockFakeLib-1.0")) + local good = false + for _, lib in pairs(libraries) do + if lib.name == "LibRockFakeLib-1.0" then + good = true + break + end + end + assert(good) + __removeLibrary("LibRockFakeLib-1.0") + for _, lib in pairs(libraries) do + assert(lib.name ~= "LibRockFakeLib-1.0") + end + assert(not LibStub:GetLibrary("LibRockFakeLib-1.0", true)) + assert(not Rock:HasLibrary("LibRockFakeLib-1.0")) +end) + +Rock:AddUnitTest(MAJOR_VERSION, function() + -- test library creation and the like + assert(not Rock:HasLibrary("LibRockFakeLib-1.0")) + for name in Rock:IterateLibraries() do + assert(name ~= "LibRockFakeLib-1.0") + end + + local myLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 1) + assert(myLib) + assert(myLib.name == "LibRockFakeLib-1.0") + assert(not oldLib) + + assert(myLib:GetLibraryVersion() == "LibRockFakeLib-1.0") + assert(select(2, myLib:GetLibraryVersion()) == 1) + + local good = false + for name in Rock:IterateLibraries() do + if name == "LibRockFakeLib-1.0" then + good = true + break + end + end + assert(good) + assert(Rock:HasLibrary("LibRockFakeLib-1.0")) + assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib) + assert(Rock("LibRockFakeLib-1.0") == myLib) + + assert(not Rock:IsLibraryMixin("LibRockFakeLib-1.0")) + function myLib:DoSomething() + return "Something" + end + myLib:SetExportedMethods("DoSomething") + assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0")) + local t = {} + assert(not Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0")) + assert(not t.DoSomething) + for mixin in Rock:IterateObjectMixins(t) do + assert(false) + end + for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do + assert(false) + end + myLib:Embed(t) + assert(t:DoSomething() == "Something") + assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0")) + for mixin in Rock:IterateObjectMixins(t) do + assert(mixin == myLib) + end + for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do + assert(object == t) + end + + Rock:FinalizeLibrary("LibRockFakeLib-1.0") + + local myNewLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 2) + assert(myNewLib == myLib) + assert(oldLib) + assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib) + + function myLib:DoSomething() + return "Something else" + end + function myLib:TrySomething() + return "Blah" + end + myLib:SetExportedMethods("DoSomething", "TrySomething") + assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0")) + assert(t:DoSomething() == "Something else") + assert(t:TrySomething() == "Blah") + assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0")) + for mixin in Rock:IterateObjectMixins(t) do + assert(mixin == myLib) + end + for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do + assert(object == t) + end + + Rock:FinalizeLibrary("LibRockFakeLib-1.0") + + local myNewLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 3) + assert(myNewLib == myLib) + assert(oldLib) + assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib) + + function myLib:DoSomething() + return "Something" + end + myLib:SetExportedMethods("DoSomething") + assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0")) + assert(t:DoSomething() == "Something") + assert(t.TrySomething == nil) + assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0")) + for mixin in Rock:IterateObjectMixins(t) do + assert(mixin == myLib) + end + for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do + assert(object == t) + end + + Rock:FinalizeLibrary("LibRockFakeLib-1.0") + + assert(not Rock:NewLibrary("LibRockFakeLib-1.0", 2)) -- out of date + assert(not Rock:NewLibrary("LibRockFakeLib-1.0", 3)) -- same revision +end) + +Rock:AddUnitTest(MAJOR_VERSION, function() + assert(not Rock:HasAddon("RockFakeAddon")) + for name in Rock:IterateAddons() do + assert(name ~= "RockFakeAddon") + end + + local myAddon = Rock:NewAddon("RockFakeAddon") + + assert(myAddon) + assert(myAddon.name == "RockFakeAddon") + + local good = false + for name in Rock:IterateAddons() do + if name == "RockFakeAddon" then + good = true + break + end + end + assert(good) + assert(Rock:HasAddon("RockFakeAddon")) + assert(Rock:GetAddon("RockFakeAddon") == myAddon) +end) + +Rock:AddUnitTest(MAJOR_VERSION, function() + -- test :OnLibraryLoad + local lib = Rock:NewLibrary("LibRockFakeLib-1.0", 1) + local triggered = false + function lib:OnLibraryLoad(major, instance) + if major == "LibRockFakeLib-2.0" then + triggered = true + end + end + Rock:FinalizeLibrary("LibRockFakeLib-1.0") + + local lib = Rock:NewLibrary("LibRockFakeLib-2.0", 1) + assert(not triggered) + Rock:FinalizeLibrary("LibRockFakeLib-2.0") + assert(triggered) + triggered = false + local lib = Rock:NewLibrary("LibRockFakeLib-2.0", 2) + assert(not triggered) + Rock:FinalizeLibrary("LibRockFakeLib-2.0") + assert(not triggered) +end)