--[[ 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", } elseif GetLocale() == "ruRU" then CATEGORIES = { ["Action Bars"] = "Панели действий", ["Auction"] = "Аукцион", ["Audio"] = "Звук", ["Battlegrounds/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"] = "Интерфейс юнитов", } 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." elseif GetLocale() == "ruRU" then L["Advanced options"] = "Продвинутые опции" L["Advanced options for developers and power users."] = "Продвинутые опции для разработчиков и провинутых пользователей." 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-Табнитесь из игры, откройте избранное и вставьте ссылку в поле адрес." L["Enabled"] = "Включен" L["Enable or disable this 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 precondition(object, "methodName", func(self, ...)) * postcondition is in the form of either postcondition(object, "methodName", func(returnValues, self, ...)) or postcondition(object, "methodName", func(oldValues, returnValues, self, ...), populateOld(oldValues, self, ...)) ** 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 argCheck(value, n, "type1" [, "type2", ...]) ** 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)