comparison 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
comparison
equal deleted inserted replaced
29:9c89042bc328 30:0d95ce7a9ec2
1 --[[
2 Name: LibRock-1.0
3 Revision: $Rev: 63317 $
4 Developed by: ckknight (ckknight@gmail.com)
5 Website: http://www.wowace.com/
6 Description: Library to allow for library and addon creation and easy table recycling functions.
7 License: LGPL v2.1
8 ]]
9
10 local MAJOR_VERSION = "LibRock-1.0"
11 local MINOR_VERSION = tonumber(("$Revision: 63317 $"):match("(%d+)")) - 60000
12
13 local _G = _G
14 local GetLocale = _G.GetLocale
15 local CATEGORIES
16 if GetLocale() == "deDE" then
17 CATEGORIES = {
18 ["Action Bars"] = "Aktionsleisten",
19 ["Auction"] = "Auktion",
20 ["Audio"] = "Audio",
21 ["Battlegrounds/PvP"] = "Schlachtfeld/PvP",
22 ["Buffs"] = "Stärkungszauber",
23 ["Chat/Communication"] = "Chat/Kommunikation",
24 ["Druid"] = "Druide",
25 ["Hunter"] = "Jäger",
26 ["Mage"] = "Magier",
27 ["Paladin"] = "Paladin",
28 ["Priest"] = "Priester",
29 ["Rogue"] = "Schurke",
30 ["Shaman"] = "Schamane",
31 ["Warlock"] = "Hexenmeister",
32 ["Warrior"] = "Krieger",
33 ["Healer"] = "Heiler",
34 ["Tank"] = "Tank",
35 ["Caster"] = "Zauberer",
36 ["Combat"] = "Kampf",
37 ["Compilations"] = "Zusammenstellungen",
38 ["Data Export"] = "Datenexport",
39 ["Development Tools"] = "Entwicklungstools",
40 ["Guild"] = "Gilde",
41 ["Frame Modification"] = "Frameveränderungen",
42 ["Interface Enhancements"] = "Interfaceverbesserungen",
43 ["Inventory"] = "Inventar",
44 ["Library"] = "Bibliotheken",
45 ["Map"] = "Karte",
46 ["Mail"] = "Post",
47 ["Miscellaneous"] = "Diverses",
48 ["Quest"] = "Quest",
49 ["Raid"] = "Schlachtzug",
50 ["Tradeskill"] = "Beruf",
51 ["UnitFrame"] = "Einheiten-Fenster",
52 }
53 elseif GetLocale() == "frFR" then
54 CATEGORIES = {
55 ["Action Bars"] = "Barres d'action",
56 ["Auction"] = "Hôtel des ventes",
57 ["Audio"] = "Audio",
58 ["Battlegrounds/PvP"] = "Champs de bataille/JcJ",
59 ["Buffs"] = "Buffs",
60 ["Chat/Communication"] = "Chat/Communication",
61 ["Druid"] = "Druide",
62 ["Hunter"] = "Chasseur",
63 ["Mage"] = "Mage",
64 ["Paladin"] = "Paladin",
65 ["Priest"] = "Prêtre",
66 ["Rogue"] = "Voleur",
67 ["Shaman"] = "Chaman",
68 ["Warlock"] = "Démoniste",
69 ["Warrior"] = "Guerrier",
70 ["Healer"] = "Soigneur",
71 ["Tank"] = "Tank",
72 ["Caster"] = "Casteur",
73 ["Combat"] = "Combat",
74 ["Compilations"] = "Compilations",
75 ["Data Export"] = "Exportation de données",
76 ["Development Tools"] = "Outils de développement",
77 ["Guild"] = "Guilde",
78 ["Frame Modification"] = "Modification des fenêtres",
79 ["Interface Enhancements"] = "Améliorations de l'interface",
80 ["Inventory"] = "Inventaire",
81 ["Library"] = "Bibliothèques",
82 ["Map"] = "Carte",
83 ["Mail"] = "Courrier",
84 ["Miscellaneous"] = "Divers",
85 ["Quest"] = "Quêtes",
86 ["Raid"] = "Raid",
87 ["Tradeskill"] = "Métiers",
88 ["UnitFrame"] = "Fenêtres d'unité",
89 }
90 elseif GetLocale() == "koKR" then
91 CATEGORIES = {
92 ["Action Bars"] = "액션바",
93 ["Auction"] = "경매",
94 ["Audio"] = "음향",
95 ["Battlegrounds/PvP"] = "전장/PvP",
96 ["Buffs"] = "버프",
97 ["Chat/Communication"] = "대화/의사소통",
98 ["Druid"] = "드루이드",
99 ["Hunter"] = "사냥꾼",
100 ["Mage"] = "마법사",
101 ["Paladin"] = "성기사",
102 ["Priest"] = "사제",
103 ["Rogue"] = "도적",
104 ["Shaman"] = "주술사",
105 ["Warlock"] = "흑마법사",
106 ["Warrior"] = "전사",
107 ["Healer"] = "힐러",
108 ["Tank"] = "탱커",
109 ["Caster"] = "캐스터",
110 ["Combat"] = "전투",
111 ["Compilations"] = "복합",
112 ["Data Export"] = "자료 출력",
113 ["Development Tools"] = "개발 도구",
114 ["Guild"] = "길드",
115 ["Frame Modification"] = "구조 변경",
116 ["Interface Enhancements"] = "인터페이스 강화",
117 ["Inventory"] = "인벤토리",
118 ["Library"] = "라이브러리",
119 ["Map"] = "지도",
120 ["Mail"] = "우편",
121 ["Miscellaneous"] = "기타",
122 ["Quest"] = "퀘스트",
123 ["Raid"] = "공격대",
124 ["Tradeskill"] = "전문기술",
125 ["UnitFrame"] = "유닛 프레임",
126 }
127 elseif GetLocale() == "zhTW" then
128 CATEGORIES = {
129 ["Action Bars"] = "動作列",
130 ["Auction"] = "拍賣",
131 ["Audio"] = "音效",
132 ["Battlegrounds/PvP"] = "戰場/PvP",
133 ["Buffs"] = "增益",
134 ["Chat/Communication"] = "聊天/通訊",
135 ["Druid"] = "德魯伊",
136 ["Hunter"] = "獵人",
137 ["Mage"] = "法師",
138 ["Paladin"] = "聖騎士",
139 ["Priest"] = "牧師",
140 ["Rogue"] = "盜賊",
141 ["Shaman"] = "薩滿",
142 ["Warlock"] = "術士",
143 ["Warrior"] = "戰士",
144 ["Healer"] = "治療者",
145 ["Tank"] = "坦克",
146 ["Caster"] = "施法者",
147 ["Combat"] = "戰鬥",
148 ["Compilations"] = "整合",
149 ["Data Export"] = "資料匯出",
150 ["Development Tools"] = "開發工具",
151 ["Guild"] = "公會",
152 ["Frame Modification"] = "框架修改",
153 ["Interface Enhancements"] = "介面增強",
154 ["Inventory"] = "庫存",
155 ["Library"] = "程式庫",
156 ["Map"] = "地圖",
157 ["Mail"] = "郵件",
158 ["Miscellaneous"] = "雜項",
159 ["Quest"] = "任務",
160 ["Raid"] = "團隊",
161 ["Tradeskill"] = "交易技能",
162 ["UnitFrame"] = "頭像框架",
163 }
164 elseif GetLocale() == "zhCN" then
165 CATEGORIES = {
166 ["Action Bars"] = "动作条",
167 ["Auction"] = "拍卖",
168 ["Audio"] = "音频",
169 ["Battlegrounds/PvP"] = "战场/PvP",
170 ["Buffs"] = "增益魔法",
171 ["Chat/Communication"] = "聊天/交流",
172 ["Druid"] = "德鲁伊",
173 ["Hunter"] = "猎人",
174 ["Mage"] = "法师",
175 ["Paladin"] = "圣骑士",
176 ["Priest"] = "牧师",
177 ["Rogue"] = "潜行者",
178 ["Shaman"] = "萨满祭司",
179 ["Warlock"] = "术士",
180 ["Warrior"] = "战士",
181 ["Healer"] = "治疗",
182 ["Tank"] = "坦克",
183 ["Caster"] = "远程输出",
184 ["Combat"] = "战斗",
185 ["Compilations"] = "编译",
186 ["Data Export"] = "数据导出",
187 ["Development Tools"] = "开发工具",
188 ["Guild"] = "公会",
189 ["Frame Modification"] = "框架修改",
190 ["Interface Enhancements"] = "界面增强",
191 ["Inventory"] = "背包",
192 ["Library"] = "库",
193 ["Map"] = "地图",
194 ["Mail"] = "邮件",
195 ["Miscellaneous"] = "杂项",
196 ["Quest"] = "任务",
197 ["Raid"] = "团队",
198 ["Tradeskill"] = "商业技能",
199 ["UnitFrame"] = "头像框架",
200 }
201 elseif GetLocale() == "esES" then
202 CATEGORIES = {
203 ["Action Bars"] = "Barras de Acción",
204 ["Auction"] = "Subasta",
205 ["Audio"] = "Audio",
206 ["Battlegrounds/PvP"] = "Campos de Batalla/JcJ",
207 ["Buffs"] = "Buffs",
208 ["Chat/Communication"] = "Chat/Comunicación",
209 ["Druid"] = "Druida",
210 ["Hunter"] = "Cazador",
211 ["Mage"] = "Mago",
212 ["Paladin"] = "Paladín",
213 ["Priest"] = "Sacerdote",
214 ["Rogue"] = "Pícaro",
215 ["Shaman"] = "Chamán",
216 ["Warlock"] = "Brujo",
217 ["Warrior"] = "Guerrero",
218 ["Healer"] = "Sanador",
219 ["Tank"] = "Tanque",
220 ["Caster"] = "Conjurador",
221 ["Combat"] = "Combate",
222 ["Compilations"] = "Compilaciones",
223 ["Data Export"] = "Exportar Datos",
224 ["Development Tools"] = "Herramientas de Desarrollo",
225 ["Guild"] = "Hermandad",
226 ["Frame Modification"] = "Modificación de Marcos",
227 ["Interface Enhancements"] = "Mejoras de la Interfaz",
228 ["Inventory"] = "Inventario",
229 ["Library"] = "Biblioteca",
230 ["Map"] = "Mapa",
231 ["Mail"] = "Correo",
232 ["Miscellaneous"] = "Misceláneo",
233 ["Quest"] = "Misión",
234 ["Raid"] = "Banda",
235 ["Tradeskill"] = "Habilidad de Comercio",
236 ["UnitFrame"] = "Marco de Unidades",
237 }
238 else -- enUS
239 CATEGORIES = {
240 ["Action Bars"] = "Action Bars",
241 ["Auction"] = "Auction",
242 ["Audio"] = "Audio",
243 ["Battlegrounds/PvP"] = "Battlegrounds/PvP",
244 ["Buffs"] = "Buffs",
245 ["Chat/Communication"] = "Chat/Communication",
246 ["Druid"] = "Druid",
247 ["Hunter"] = "Hunter",
248 ["Mage"] = "Mage",
249 ["Paladin"] = "Paladin",
250 ["Priest"] = "Priest",
251 ["Rogue"] = "Rogue",
252 ["Shaman"] = "Shaman",
253 ["Warlock"] = "Warlock",
254 ["Warrior"] = "Warrior",
255 ["Healer"] = "Healer",
256 ["Tank"] = "Tank",
257 ["Caster"] = "Caster",
258 ["Combat"] = "Combat",
259 ["Compilations"] = "Compilations",
260 ["Data Export"] = "Data Export",
261 ["Development Tools"] = "Development Tools",
262 ["Guild"] = "Guild",
263 ["Frame Modification"] = "Frame Modification",
264 ["Interface Enhancements"] = "Interface Enhancements",
265 ["Inventory"] = "Inventory",
266 ["Library"] = "Library",
267 ["Map"] = "Map",
268 ["Mail"] = "Mail",
269 ["Miscellaneous"] = "Miscellaneous",
270 ["Quest"] = "Quest",
271 ["Raid"] = "Raid",
272 ["Tradeskill"] = "Tradeskill",
273 ["UnitFrame"] = "UnitFrame",
274 }
275 end
276
277 local select = _G.select
278 local tostring = _G.tostring
279 local pairs = _G.pairs
280 local ipairs = _G.ipairs
281 local error = _G.error
282 local setmetatable = _G.setmetatable
283 local getmetatable = _G.getmetatable
284 local type = _G.type
285 local pcall = _G.pcall
286 local next = _G.next
287 local tonumber = _G.tonumber
288 local strmatch = _G.strmatch
289 local table_remove = _G.table.remove
290 local debugstack = _G.debugstack
291 local LoadAddOn = _G.LoadAddOn
292 local GetAddOnInfo = _G.GetAddOnInfo
293 local GetAddOnMetadata = _G.GetAddOnMetadata
294 local GetNumAddOns = _G.GetNumAddOns
295 local DisableAddOn = _G.DisableAddOn
296 local EnableAddOn = _G.EnableAddOn
297 local IsAddOnLoadOnDemand = _G.IsAddOnLoadOnDemand
298 local IsLoggedIn = _G.IsLoggedIn
299 local geterrorhandler = _G.geterrorhandler
300 local assert = _G.assert
301 local collectgarbage = _G.collectgarbage
302 local table_sort = _G.table.sort
303 local table_concat = _G.table.concat
304
305 -- #AUTODOC_NAMESPACE Rock
306
307
308 local LibStub = _G.LibStub
309
310 local Rock = LibStub:GetLibrary(MAJOR_VERSION, true) or _G.Rock
311 local oldRock
312 if not Rock then
313 Rock = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
314 if not Rock then
315 return
316 end
317 Rock.name = MAJOR_VERSION
318 else
319 Rock, oldRock = Rock:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
320 if not Rock then
321 return
322 end
323 end
324 _G.Rock = Rock
325
326 local L = setmetatable({}, {__index=function(self,key) self[key] = key; return key end})
327 if GetLocale() == "zhCN" then
328 L["Advanced options"] = "高级选项"
329 L["Advanced options for developers and power users."] = "开发者与高级用户的高级选项"
330 L["Unit tests"] = "框体测试"
331 L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "开启框体测试,仅供开发者使用。\n\n需要重载用户界面。"
332 L["Contracts"] = "侦错协定"
333 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."] = "启用侦错协定,这是给插件作者用来通报错误所使用。"
334 L["Reload UI"] = "重载UI"
335 L["Reload the User Interface for some changes to take effect."] = "部分功能更改需要重载用户界面才会生效。"
336 L["Reload"] = "重载"
337 L["Give donation"] = "捐赠"
338 L["Donate"] = "捐赠"
339 L["Give a much-needed donation to the author of this addon."] = "给插件作者捐赠支持插件开发。"
340 L["File issue"] = "通报错误"
341 L["Report"] = "报告"
342 L["File a bug or request a new feature or an improvement to this addon."] = "发送错误报告或请求新功能及要改进的部分。"
343 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切换到桌面,打开浏览器,在地址栏贴上网址。"
344 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切换到电脑桌面,打开浏览器,在地址栏贴上网址。"
345 L["Enabled"] = "开启"
346 L["Enable or disable this addon."] = "启用这个插件。"
347
348 elseif GetLocale() == "zhTW" then
349 L["Advanced options"] = "進階選項"
350 L["Advanced options for developers and power users."] = "插件作者、進階用戶選項"
351 L["Unit tests"] = "單元測試"
352 L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "啟用單元測試,這是給插件作者使用的功能。\n\n需要重載介面才能使用。"
353 L["Contracts"] = "偵錯協定"
354 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."] = "啟用偵錯協定,這是給插件作者用來通報錯誤所使用。"
355 L["Reload UI"] = "重載介面"
356 L["Reload the User Interface for some changes to take effect."] = "重新載入使用者介面,部分功能才會生效。"
357 L["Reload"] = "重載"
358 L["Give donation"] = "捐贈"
359 L["Donate"] = "捐贈"
360 L["Give a much-needed donation to the author of this addon."] = "捐贈金錢給插件作者。"
361 L["File issue"] = "通報錯誤"
362 L["Report"] = "報告"
363 L["File a bug or request a new feature or an improvement to this addon."] = "發出錯誤報告或請求新功能及要改進的部分。"
364 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切換到電腦桌面,打開瀏覽器,在網址列貼上網址。"
365 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切換到電腦桌面,打開瀏覽器,在網址列貼上網址。"
366 L["Enabled"] = "啟用"
367 L["Enable or disable this addon."] = "啟用這個插件。"
368 elseif GetLocale() == "koKR" then
369 L["Advanced options"] = "상세 옵션"
370 L["Advanced options for developers and power users."] = "개발자와 파워 사용자를 위한 상세 옵션입니다."
371 L["Unit tests"] = "유닛 테스트"
372 L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "유닛 테스트를 사용합니다. 이것은 개발자만을 위한 옵션입니다.\n\n변경된 결과를 적용하기 위해 당신의 UI를 재실행 합니다."
373 L["Contracts"] = "계약"
374 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."] = "계약을 사용합니다. 이것은 개발자와 버그 파일을 알릴 분이면 누구나 사용 가능합니다. 계약이 가능하지 않으면 버그 파일을 보내지 마십시오. 이것은 당신의 애드온 속도를 약간 떨어뜨립니다."
375 L["Reload UI"] = "UI 재실행"
376 L["Reload the User Interface for some changes to take effect."] = "변경된 결과를 적용하기 위해 사용자 인터페이스를 재실행합니다."
377 L["Reload"] = "재실행"
378 L["Give donation"] = "기부"
379 L["Donate"] = "기부"
380 L["Give a much-needed donation to the author of this addon."] = "이 애드온의 제작자에게 필요한 기부를 합니다."
381 L["File issue"] = "파일 이슈"
382 L["Report"] = "보고"
383 L["File a bug or request a new feature or an improvement to this addon."] = "버그 파일을 알리거나 새로운 기능 또는 이 애드온에 대한 개선을 부탁합니다."
384 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 눌려 게임으로 부터 나간후 웹 브라우저를 엽니다. 복사된 링크를 주소 창에 붙여넣기 합니다."
385 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 눌려 게임으로 부터 나간후 웹 브라우저를 엽니다. 복사된 링크를 주소 창에 붙여넣기 합니다."
386 L["Enabled"] = "사용"
387 L["Enable or disable this addon."] = "이 애드온을 사용하거나 사용하지 않습니다."
388 elseif GetLocale() == "frFR" then
389 L["Advanced options"] = "Options avancées"
390 L["Advanced options for developers and power users."] = "Options avancées à l'attention des développeurs et des utilisateurs expérimentés."
391 L["Reload UI"] = "Recharger IU"
392 L["Reload the User Interface for some changes to take effect."] = "Recharge l'interface utilisateur afin que certains changements prennent effet."
393 L["Reload"] = "Recharger"
394 L["Give donation"] = "Faire un don"
395 L["Donate"] = "Don"
396 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."
397 L["File issue"] = "Problème"
398 L["Report"] = "Signaler"
399 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."
400 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."
401 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."
402 L["Enabled"] = "Activé"
403 L["Enable or disable this addon."] = "Active ou désactive cet addon."
404 end
405
406 local isStandalone = debugstack():match("[Oo%.][Nn%.][Ss%.]\\([^\\]+)\\") == MAJOR_VERSION or nil
407 local unitTestDB, enableContracts
408
409 local weakKey = { __mode = 'k' }
410
411 -- frame to manage events from
412 Rock.frame = oldRock and oldRock.frame or _G.CreateFrame("Frame")
413 local frame = Rock.frame
414 -- dict of libraries in { ["major"] = object } form
415 Rock.libraries = oldRock and oldRock.libraries or { [MAJOR_VERSION] = Rock }
416 local libraries = Rock.libraries
417 -- set of libraries which have gone through the finalization process in { [object] = true } form
418 Rock.finalizedLibraries = setmetatable(oldRock and oldRock.finalizedLibraries or { }, weakKey)
419 local finalizedLibraries = Rock.finalizedLibraries
420 -- set of libraries which have been tried to be loaded.
421 Rock.scannedLibraries = oldRock and oldRock.scannedLibraries or {}
422 local scannedLibraries = Rock.scannedLibraries
423 -- exportedMethods[library] = { "method1", "method2" }
424 Rock.exportedMethods = setmetatable(oldRock and oldRock.exportedMethods or {}, weakKey)
425 local exportedMethods = Rock.exportedMethods
426 -- mixinToObject[mixin][object] = true
427 Rock.mixinToObject = setmetatable(oldRock and oldRock.mixinToObject or {}, weakKey)
428 local mixinToObject = Rock.mixinToObject
429 -- dict of addons in { ["name"] = object } form
430 Rock.addons = oldRock and oldRock.addons or {}
431 local addons = Rock.addons
432 -- set of libraries that should be finalized before ADDON_LOADED.
433 Rock.pendingLibraries = setmetatable(oldRock and oldRock.pendingLibraries or { }, weakKey)
434 local pendingLibraries = Rock.pendingLibraries
435 -- list of addons in order of created that need to be initialized by ADDON_LOADED.
436 Rock.pendingAddons = oldRock and oldRock.pendingAddons or {}
437 local pendingAddons = Rock.pendingAddons
438 -- dictionary of addons to their folder names
439 Rock.addonToFolder = oldRock and oldRock.addonToFolder or {}
440 local addonToFolder = Rock.addonToFolder
441 -- set of folders which have been loaded
442 Rock.foldersLoaded = oldRock and oldRock.foldersLoaded or {}
443 local foldersLoaded = Rock.foldersLoaded
444 -- list of addons in order of created that need to be enabled by PLAYER_LOGIN.
445 Rock.pendingAddonsEnable = oldRock and oldRock.pendingAddonsEnable or {}
446 local pendingAddonsEnable = Rock.pendingAddonsEnable
447 -- set of addons which have been enabled at least once.
448 Rock.addonsAlreadyEnabled = oldRock and oldRock.addonsAlreadyEnabled or {}
449 local addonsAlreadyEnabled = Rock.addonsAlreadyEnabled
450 -- set of addons which have no database and are set to be inactive.
451 Rock.inactiveAddons = oldRock and oldRock.inactiveAddons or {}
452 local inactiveAddons = Rock.inactiveAddons
453 -- set of addons which are currently enabled (not necessarily should be)
454 Rock.currentlyEnabledAddons = oldRock and oldRock.currentlyEnabledAddons or {}
455 local currentlyEnabledAddons = Rock.currentlyEnabledAddons
456 -- dictionary of namespace to list of functions which will be run.
457 Rock.unitTests = oldRock and oldRock.unitTests or {}
458 local unitTests = Rock.unitTests
459 -- metatable for addons
460 Rock.addon_mt = oldRock and oldRock.addon_mt or {}
461 local addon_mt = Rock.addon_mt
462 for k in pairs(addon_mt) do
463 addon_mt[k] = nil
464 end
465 function addon_mt:__tostring()
466 return tostring(self.name)
467 end
468
469 local function better_tostring(self)
470 if type(self) == "table" and self.name then
471 return tostring(self.name)
472 end
473 return tostring(self)
474 end
475
476 local function figureCurrentAddon(pos)
477 local stack = debugstack(pos+1, 1, 0)
478 local folder = stack:match("[Oo%.][Nn%.][Ss%.]\\([^\\]+)\\")
479 if folder then
480 return folder
481 end
482
483 local partFolder = stack:match("...([^\\]+)\\")
484 if partFolder then
485 local partFolder_len = #partFolder
486 for i = 1, GetNumAddOns() do
487 local name = GetAddOnInfo(i)
488 if #name >= partFolder_len then
489 local partName = name:sub(-partFolder_len)
490 if partName == partFolder then
491 return name
492 end
493 end
494 end
495 end
496 return nil
497 end
498
499 --[[---------------------------------------------------------------------------
500 Returns:
501 string - the localized name of the given category.
502 Arguments:
503 string - the English name of the category.
504 Example:
505 local uf = Rock:GetLocalizedCategory("UnitFrame")
506 -----------------------------------------------------------------------------]]
507 function Rock:GetLocalizedCategory(name)
508 if type(name) ~= "string" then
509 error(("Bad argument #2 to `GetLocalizedCategory'. Expected %q, got %q."):format("string", type(name)), 2)
510 end
511 local cat = CATEGORIES[name]
512 if cat then
513 return cat
514 end
515 local name_lower = name:lower()
516 for k in pairs(CATEGORIES) do
517 if k:lower() == name_lower then
518 return k
519 end
520 end
521 return _G.UNKNOWN or "Unknown"
522 end
523
524 local weak = {__mode = 'kv'}
525
526 Rock.recycleData = oldRock and oldRock.recycleData or {}
527 local recycleData = Rock.recycleData
528 if recycleData.pools then
529 setmetatable(recycleData.pools, weak)
530 end
531 if recycleData.debugPools then
532 setmetatable(recycleData.debugPools, weak)
533 end
534 if recycleData.newList then
535 setmetatable(recycleData.newList, weak)
536 end
537 if recycleData.newDict then
538 setmetatable(recycleData.newDict, weak)
539 end
540 if recycleData.newSet then
541 setmetatable(recycleData.newSet, weak)
542 end
543 if recycleData.del then
544 setmetatable(recycleData.del, weak)
545 end
546
547 local tmp = {}
548 local function myUnpack(t, start)
549 if not start then
550 start = 1
551 end
552 local value = t[start]
553 if value == nil then
554 return
555 end
556 t[start] = nil
557 return value, myUnpack(t, start+1)
558 end
559
560 --[[---------------------------------------------------------------------------
561 Notes:
562 * Returns functions for the specified namespace based on what is provided.
563 * function types:
564 ; "newList" : to create a list
565 ; "newDict" : to create a dictionary
566 ; "newSet" : to create a set
567 ; "del" : to delete a table
568 ; "unpackListAndDel" : deletes a table and returns what its contents were as a list, in order.
569 ; "unpackSetAndDel" : deletes a table and returns what its contents were as a set, in no particular order.
570 ; "unpackDictAndDel" : deletes a table and returns what its contents were as a dictionary, in no particular order.
571 * If you provide "Debug" as the last argument, then the namespace can be debugged with ''':DebugRecycle'''
572 * 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.
573 Arguments:
574 string - the namespace. ''Note: this doesn't necessarily have to be a string.''
575 Example:
576 local newList, newDict, newSet, del, unpackListAndDel, unpackSetAndDel, unpackDictAndDel = Rock:GetRecyclingFunctions("MyNamespace", "newList", "newDict", "newSet", "del", "unpackListAndDel", "unpackSetAndDel", "unpackDictAndDel")
577
578 local t = newList('alpha', 'bravo') -- same as t = {'alpha', 'bravo'}
579 local u = newDict('alpha', 'bravo') -- same as t = {['alpha'] = 'bravo'}
580 local v = newSet('alpha', 'bravo') -- same as t = {['alpha'] = true, ['bravo'] = true}
581 t = del(t) -- you want to clear your reference as well as deleting.
582 u = del(u)
583 v = del(v)
584
585 -- for debugging
586 local newList = Rock:GetRecyclingFunctions("MyNamespace", "newList", "Debug")
587 local t = newList()
588 Rock:DebugRecycle("MyNamespace")
589 t = del(t)
590
591 -- unpacking functions
592 unpackListAndDel(newList(...)) => ...
593 unpackSetAndDel(newSet(...)) => ...
594 unpackDictAndDel(newDict(...)) => ...
595 newList(unpackListAndDel(t)) => t
596 newSet(unpackSetAndDel(t)) => t
597 newDict(unpackDictAndDel(t)) => t
598 -- as you can see, they are inverses of each other.
599 -----------------------------------------------------------------------------]]
600 function Rock:GetRecyclingFunctions(namespace, ...)
601 local pools = recycleData.pools
602 if not pools then
603 pools = setmetatable({}, weak)
604 recycleData.pools = pools
605 end
606 if namespace == "newList" or namespace == "newSet" or namespace == "newDict" or namespace == "del" or namespace == "unpackListAndDel" or namespace == "unpackSetAndDel" or namespace == "unpackDictAndDel" then
607 error(("Bad argument #2 to `GetRecyclingFunctions'. Cannot be %q"):format(namespace), 2)
608 end
609 local pool = pools[namespace]
610 if not pool then
611 pool = setmetatable({}, weak)
612 pools[namespace] = pool
613 end
614 local n = select('#', ...)
615 local debug = select(n, ...) == "Debug"
616 if debug then
617 n = n - 1
618 local debugPools = recycleData.debugPools
619 if not debugPools then
620 debugPools = setmetatable({}, weak)
621 recycleData.debugPools = debugPools
622 end
623 debug = debugPools[namespace]
624 if not debug then
625 debug = { num = 0 }
626 debugPools[namespace] = debug
627 end
628 elseif recycleData.debugPools and recycleData.debugPools[namespace] then
629 debug = recycleData.debugPools[namespace]
630 end
631 for i = 1, n do
632 local func = select(i, ...)
633 local recycleData_func = recycleData[func]
634 if not recycleData_func then
635 recycleData_func = setmetatable({}, weak)
636 recycleData[func] = recycleData_func
637 end
638 if func == "newList" then
639 local newList = recycleData_func[namespace]
640 if not newList then
641 function newList(...)
642 local t = next(pool)
643 local n = select('#', ...)
644 if t then
645 pool[t] = nil
646 for i = 1, n do
647 t[i] = select(i, ...)
648 end
649 else
650 t = { ... }
651 end
652
653 if debug then
654 debug[t] = debugstack(2)
655 debug.num = debug.num + 1
656 end
657
658 return t, n
659 end
660 recycleData_func[namespace] = newList
661 end
662 tmp[i] = newList
663 elseif func == "newDict" then
664 local newDict = recycleData_func[namespace]
665 if not newDict then
666 function newDict(...)
667 local t = next(pool)
668 if t then
669 pool[t] = nil
670 else
671 t = {}
672 end
673
674 for i = 1, select('#', ...), 2 do
675 t[select(i, ...)] = select(i+1, ...)
676 end
677
678 if debug then
679 debug[t] = debugstack(2)
680 debug.num = debug.num + 1
681 end
682
683 return t
684 end
685 recycleData_func[namespace] = newDict
686 end
687 tmp[i] = newDict
688 elseif func == "newSet" then
689 local newSet = recycleData_func[namespace]
690 if not newSet then
691 function newSet(...)
692 local t = next(pool)
693 if t then
694 pool[t] = nil
695 else
696 t = {}
697 end
698
699 for i = 1, select('#', ...) do
700 t[select(i, ...)] = true
701 end
702
703 if debug then
704 debug[t] = debugstack(2)
705 debug.num = debug.num + 1
706 end
707
708 return t
709 end
710 recycleData_func[namespace] = newSet
711 end
712 tmp[i] = newSet
713 elseif func == "del" then
714 local del = recycleData_func[namespace]
715 if not del then
716 function del(t)
717 if not t then
718 error(("Bad argument #1 to `del'. Expected %q, got %q."):format("table", type(t)), 2)
719 end
720 if pool[t] then
721 local _, ret = pcall(error, "Error, double-free syndrome.", 3)
722 geterrorhandler()(ret)
723 end
724 setmetatable(t, nil)
725 for k in pairs(t) do
726 t[k] = nil
727 end
728 t[true] = true
729 t[true] = nil
730 pool[t] = true
731
732 if debug then
733 debug[t] = nil
734 debug.num = debug.num - 1
735 end
736 return nil
737 end
738 recycleData_func[namespace] = del
739 end
740 tmp[i] = del
741 elseif func == "unpackListAndDel" then
742 local unpackListAndDel = recycleData_func[namespace]
743 if not unpackListAndDel then
744 local function f(t, start, finish)
745 if start > finish then
746 for k in pairs(t) do
747 t[k] = nil
748 end
749 t[true] = true
750 t[true] = nil
751 pool[t] = true
752 return
753 end
754 return t[start], f(t, start+1, finish)
755 end
756 function unpackListAndDel(t, start, finish)
757 if not t then
758 error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2)
759 end
760 if not start then
761 start = 1
762 end
763 if not finish then
764 finish = #t
765 end
766 setmetatable(t, nil)
767 if debug then
768 debug[t] = nil
769 debug.num = debug.num - 1
770 end
771 return f(t, start, finish)
772 end
773 end
774 tmp[i] = unpackListAndDel
775 elseif func == "unpackSetAndDel" then
776 local unpackSetAndDel = recycleData_func[namespace]
777 if not unpackSetAndDel then
778 local function f(t, current)
779 current = next(t, current)
780 if current == nil then
781 for k in pairs(t) do
782 t[k] = nil
783 end
784 t[true] = true
785 t[true] = nil
786 pool[t] = true
787 return
788 end
789 return current, f(t, current)
790 end
791 function unpackSetAndDel(t)
792 if not t then
793 error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2)
794 end
795 setmetatable(t, nil)
796 if debug then
797 debug[t] = nil
798 debug.num = debug.num - 1
799 end
800 return f(t, nil)
801 end
802 end
803 tmp[i] = unpackSetAndDel
804 elseif func == "unpackDictAndDel" then
805 local unpackDictAndDel = recycleData_func[namespace]
806 if not unpackDictAndDel then
807 local function f(t, current)
808 local value
809 current, value = next(t, current)
810 if current == nil then
811 for k in pairs(t) do
812 t[k] = nil
813 end
814 t[true] = true
815 t[true] = nil
816 pool[t] = true
817 return
818 end
819 return current, value, f(t, current)
820 end
821 function unpackDictAndDel(t)
822 if not t then
823 error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2)
824 end
825 setmetatable(t, nil)
826 if debug then
827 debug[t] = nil
828 debug.num = debug.num - 1
829 end
830 return f(t, nil)
831 end
832 end
833 tmp[i] = unpackDictAndDel
834 else
835 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)
836 end
837 end
838 return myUnpack(tmp)
839 end
840
841 --[[---------------------------------------------------------------------------
842 Notes:
843 * Prints information about the specified recycling namespace, including what tables are still in play and where they come from and how many there are.
844 * This goes in tandem with ''':GetRecyclingFunctions'''
845 Arguments:
846 string - the namespace. ''Note: this doesn't necessarily have to be a string.''
847 Example:
848 local newList = Rock:GetRecyclingFunctions("MyNamespace", "newList", "Debug")
849 local t = newList()
850 Rock:DebugRecycle("MyNamespace")
851 t = del(t)
852 -----------------------------------------------------------------------------]]
853 function Rock:DebugRecycle(namespace)
854 local debug = recycleData.debugPools and recycleData.debugPools[namespace]
855 if not debug then
856 return
857 end
858 for k, v in pairs(debug) do
859 if k ~= "num" then
860 _G.DEFAULT_CHAT_FRAME:AddMessage(v)
861 _G.DEFAULT_CHAT_FRAME:AddMessage("------")
862 end
863 end
864 _G.DEFAULT_CHAT_FRAME:AddMessage(("%s: %d tables in action."):format(tostring(namespace), debug.num))
865 end
866
867 local newList, del, unpackListAndDel, unpackDictAndDel = Rock:GetRecyclingFunctions(MAJOR_VERSION, "newList", "del", "unpackListAndDel", "unpackDictAndDel")
868
869 --[[---------------------------------------------------------------------------
870 Notes:
871 * Adds a unit test for the specified namespace
872 * 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.
873 * You can have as many tests per namespace as you want.
874 Arguments:
875 string - the namespace.
876 function - the function to call.
877 Example:
878 Rock:AddUnitTest("LibMonkey-1.0", function()
879 local LibMonkey = Rock("LibMonkey-1.0")
880 assert(LibMonkey:Fling() == "Poo")
881 end)
882 -----------------------------------------------------------------------------]]
883 function Rock:AddUnitTest(namespace, func)
884 if not isStandalone then
885 return
886 end
887 if type(namespace) ~= "string" then
888 error(("Bad argument #2 to `AddUnitTest'. Expected %q, got %q."):format("string", type(namespace)), 2)
889 end
890 if namespace:find("^Lib[A-Z]") then
891 local addon = figureCurrentAddon(2)
892 if addon ~= namespace then
893 return
894 end
895 end
896 if type(func) ~= "function" then
897 error(("Bad argument #3 to `AddUnitTest'. Expected %q, got %q."):format("function", type(func)), 2)
898 end
899 local addon = figureCurrentAddon(2)
900 if libraries[namespace] and addon ~= namespace then
901 -- only work on standalone libraries.
902 return
903 end
904 local unitTests_namespace = unitTests[namespace]
905 if not unitTests_namespace then
906 unitTests_namespace = newList()
907 unitTests[namespace] = unitTests_namespace
908 end
909 if not unitTests_namespace.addon then
910 unitTests_namespace.addon = addon
911 end
912 if unitTestDB and not unitTestDB[namespace] then
913 return
914 end
915 unitTests_namespace[#unitTests_namespace+1] = func
916 end
917
918 local LibRockEvent
919 local LibRockModuleCore
920 local OpenDonationFrame, OpenIssueFrame
921 function Rock:OnLibraryLoad(major, library)
922 if major == "LibRockEvent-1.0" then
923 LibRockEvent = library
924 LibRockEvent:Embed(Rock)
925 elseif major == "LibRockModuleCore-1.0" then
926 LibRockModuleCore = library
927 elseif major == "LibRockConfig-1.0" then
928 if isStandalone then
929 library.rockOptions.args.advanced = {
930 type = 'group',
931 groupType = 'inline',
932 name = L["Advanced options"],
933 desc = L["Advanced options for developers and power users."],
934 order = -1,
935 args = {
936 unitTests = {
937 type = 'multichoice',
938 name = L["Unit tests"],
939 desc = L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."],
940 get = function(key)
941 return unitTestDB[key]
942 end,
943 set = function(key, value)
944 unitTestDB[key] = value or nil
945 end,
946 choices = function()
947 local t = newList()
948 for k in pairs(unitTests) do
949 t[k] = k
950 end
951 return "@dict", unpackDictAndDel(t)
952 end
953 },
954 contracts = {
955 type = 'boolean',
956 name = L["Contracts"],
957 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."],
958 get = function()
959 return enableContracts
960 end,
961 set = function(value)
962 _G.LibRock_1_0DB.contracts = value or nil
963 enableContracts = value
964 end,
965 }
966 }
967 }
968 end
969 library.rockOptions.args.reloadui = {
970 type = 'execute',
971 name = L["Reload UI"],
972 desc = L["Reload the User Interface for some changes to take effect."],
973 buttonText = L["Reload"],
974 func = function()
975 _G.ReloadUI()
976 end,
977 order = -2,
978 }
979 Rock.donate = "Paypal:ckknight AT gmail DOT com"
980 library.rockOptions.args.donate = {
981 type = 'execute',
982 name = L["Give donation"],
983 buttonText = L["Donate"],
984 desc = L["Give a much-needed donation to the author of this addon."],
985 func = OpenDonationFrame,
986 passValue = Rock,
987 order = -3,
988 }
989 Rock.issueTracker = "Wowace:10027"
990 library.rockOptions.args.issue = {
991 type = 'execute',
992 name = L["File issue"],
993 buttonText = L["Report"],
994 desc = L["File a bug or request a new feature or an improvement to this addon."],
995 func = OpenIssueFrame,
996 passValue = Rock,
997 order = -4,
998 }
999 end
1000 end
1001
1002 addon_mt.__index = {}
1003 local addon_mt___index = addon_mt.__index
1004 --[[---------------------------------------------------------------------------
1005 #FORCE_DOC
1006 Notes:
1007 * This is exported to all addons.
1008 * 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.
1009 Returns:
1010 boolean - whether the addon is in an active state or not.
1011 Example:
1012 local active = MyAddon:IsActive()
1013 -----------------------------------------------------------------------------]]
1014 function addon_mt___index:IsActive()
1015 if LibRockModuleCore then
1016 local core = LibRockModuleCore:HasModule(self)
1017 if core then
1018 return core:IsModuleActive(self)
1019 end
1020 end
1021
1022 local self_db = self.db
1023 if self_db then
1024 local disabled
1025 local self_db_raw = self_db.raw
1026 if self_db_raw then
1027 local self_db_raw_disabled = self_db_raw.disabled
1028 if self_db_raw_disabled then
1029 local profile = type(self.GetProfile) == "function" and select(2, self:GetProfile()) or false
1030 disabled = self_db_raw_disabled[profile]
1031 end
1032 else
1033 return false
1034 end
1035 return not disabled
1036 end
1037
1038 return not inactiveAddons[self]
1039 end
1040 --[[---------------------------------------------------------------------------
1041 #FORCE_DOC
1042 Notes:
1043 * This is exported to all addons.
1044 * If it enables the addon, it will call :OnEnable(first) on the addon and :OnEmbedEnable(addon, first) on all its mixins.
1045 * If it disables the addon, it will call :OnDisable(first) on the addon and :OnEmbedDisable(addon, first) on all its mixins.
1046 * 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.
1047 Arguments:
1048 [optional] boolean - whether the addon should be in an active state or not. Default: not :IsActive()
1049 Returns:
1050 boolean - whether the addon is in an active state or not.
1051 Example:
1052 MyAddon:ToggleActive() -- switch
1053 MyAddon:ToggleActive(true) -- force on
1054 MyAddon:ToggleActive(false) -- force off
1055 -----------------------------------------------------------------------------]]
1056 function addon_mt___index:ToggleActive(state)
1057 if state and state ~= true then
1058 error(("Bad argument #2 to `ToggleActive'. Expected %q or %q, got %q."):format("boolean", "nil", type(state)), 2)
1059 end
1060 if LibRockModuleCore then
1061 local core = LibRockModuleCore:HasModule(self)
1062 if core then
1063 return core:ToggleModuleActive(self, state)
1064 end
1065 end
1066
1067 local self_db = self.db
1068 if self_db then
1069 local self_db_raw = self_db.raw
1070 if not self_db_raw then
1071 error("Error saving to database with `ToggleActive'. db.raw not available.", 2)
1072 end
1073 local self_db_raw_disabled = self_db_raw.disabled
1074 if not self_db_raw_disabled then
1075 self_db_raw_disabled = newList()
1076 self_db_raw.disabled = self_db_raw_disabled
1077 end
1078 local profile = type(self.GetProfile) == "function" and select(2, self:GetProfile()) or false
1079 if state == nil then
1080 state = not not self_db_raw_disabled[profile]
1081 elseif (not self_db_raw_disabled[profile]) == state then
1082 return
1083 end
1084 self_db_raw_disabled[profile] = not state or nil
1085 if next(self_db_raw_disabled) == nil then
1086 self_db_raw.disabled = del(self_db_raw_disabled)
1087 end
1088 else
1089 if state == nil then
1090 state = not not inactiveAddons[self]
1091 elseif (not inactiveAddons[self]) == state then
1092 return
1093 end
1094 inactiveAddons[self] = not state or nil
1095 end
1096
1097 Rock:RecheckEnabledStates()
1098
1099 return state
1100 end
1101
1102 local function noop() end
1103
1104 do
1105 local preconditions = setmetatable({}, weakKey)
1106 local postconditions = setmetatable({}, weakKey)
1107 local postconditionsOld = setmetatable({}, weakKey)
1108
1109 local currentMethod = nil
1110
1111 local function hook(object, method)
1112 local object_method = object[method]
1113 object[method] = function(...)
1114 local pre = preconditions[object_method]
1115 local post = postconditions[object_method]
1116 if pre then
1117 local old_currentMethod = currentMethod
1118 currentMethod = method
1119 pre(...)
1120 currentMethod = old_currentMethod
1121 end
1122 if not post then
1123 return object_method(...)
1124 end
1125 local oldFunc = postconditionsOld[object_method]
1126 local old
1127 if oldFunc then
1128 old = newList()
1129 oldFunc(old, ...)
1130 end
1131
1132 local old_currentMethod = currentMethod
1133 currentMethod = nil
1134 local ret, n = newList(object_method(...))
1135
1136 currentMethod = method
1137 if old then
1138 post(old, ret, ...)
1139 old = del(old)
1140 else
1141 post(ret, ...)
1142 end
1143 currentMethod = old_currentMethod
1144 return unpackListAndDel(ret, 1, n)
1145 end
1146 end
1147
1148 local function precondition(object, method, func)
1149 if type(object) ~= "table" then
1150 error(("Bad argument #1 to `precondition'. Expected %q, got %q."):format("table", type(object)), 2)
1151 end
1152 if type(object[method]) ~= "function" then
1153 error(("Method %q not found on object %s. Expected %q, got %q."):format(tostring(method), tostring(object), "function", type(object[method])), 2)
1154 end
1155 if type(func) ~= "function" then
1156 error(("Bad argument #3 to `precondition'. Expected %q, got %q."):format("function", type(func)), 2)
1157 end
1158
1159 local object_method = object[method]
1160 if preconditions[object_method] then
1161 error("Cannot call `preconditon' on the same method twice.", 2)
1162 end
1163 preconditions[object_method] = func
1164
1165 if not postconditions[object_method] then
1166 hook(object, method)
1167 end
1168 end
1169
1170 local function postcondition(object, method, func, fillOld)
1171 if type(object) ~= "table" then
1172 error(("Bad argument #1 to `postcondition'. Expected %q, got %q."):format("table", type(object)), 2)
1173 end
1174 if type(object[method]) ~= "function" then
1175 error(("Method %q not found on object %s. Expected %q, got %q."):format(tostring(method), tostring(object), "function", type(object[method])), 2)
1176 end
1177 if type(func) ~= "function" then
1178 error(("Bad argument #3 to `postcondition'. Expected %q, got %q."):format("function", type(func)), 2)
1179 end
1180 if fillOld and type(fillOld) ~= "function" then
1181 error(("Bad argument #4 to `postcondition'. Expected %q or %q, got %q."):format("function", "nil", type(func)), 2)
1182 end
1183
1184 local object_method = object[method]
1185 if postconditions[object_method] then
1186 error("Cannot call `postcondition' on the same method twice.", 2)
1187 end
1188 postconditions[object_method] = func
1189 postconditionsOld[object_method] = fillOld
1190
1191 if not preconditions[object_method] then
1192 hook(object, method)
1193 end
1194 end
1195
1196 local function argCheck(value, position, ...)
1197 if not currentMethod then
1198 error("Cannot call `argCheck' outside of a pre/post-condition.", 2)
1199 end
1200 if type(position) ~= "number" then
1201 error(("Bad argument #2 to `argCheck'. Expected %q, got %q"):format("number", type(position)), 2)
1202 end
1203 local type_value = type(value)
1204 for i = 1, select('#', ...) do
1205 local v = select(i, ...)
1206 if type(v) ~= "string" then
1207 error(("Bad argument #%d to `argCheck'. Expected %q, got %q"):format(i+1, "string", type(v)), 2)
1208 end
1209 if v == type_value then
1210 return
1211 end
1212 end
1213 local t = newList(...)
1214 t[#t] = nil
1215 for i,v in ipairs(t) do
1216 t[i] = ("%q"):format(v)
1217 end
1218 local s
1219 if #t == 0 then
1220 s = ("%q"):format((...))
1221 elseif #t == 1 then
1222 s = ("%q or %q"):format(...)
1223 else
1224 s = table_concat(t, ", ") .. ", or " .. ("%q"):format(select(#t+1, ...))
1225 end
1226 t = del(t)
1227
1228 error(("Bad argument #%d to `%s'. Expected %s, got %q."):format(position, tostring(currentMethod), s, type_value), 4)
1229 end
1230
1231 --[[---------------------------------------------------------------------------
1232 Notes:
1233 * Returns functions for the specified namespace based on what is provided.
1234 * function types:
1235 ; "precondition" : to set the pre-condition for a method.
1236 ; "postcondition" : to set the post-condition for a method.
1237 ; "argCheck" : to check the type of an argument, to be executed within a pre-condition.
1238 * preconditon is in the form of <tt>precondition(object, "methodName", func(self, ...))</tt>
1239 * 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>
1240 ** returnValues is the list of return values, empty if no return values were sent.
1241 ** if the populateOld function is provided, then the empty oldValues table is provided and expected to be filled, and then given to the func.
1242 * argCheck is in the form of <tt>argCheck(value, n, "type1" [, "type2", ...])</tt>
1243 ** value is the value provided to the function you're checking.
1244 ** n is the argument position. ''Note: 1 is the position of `self'. 2 would be the first "real" position.''
1245 ** the tuple of types can be any string, but specifically "nil", "boolean", "string", "number", "function", "userdata", "table", etc.
1246 Arguments:
1247 string - the namespace. ''Note: this doesn't necessarily have to be a string.''
1248 Example:
1249 local precondition, postcondition, argCheck = Rock:GetRecyclingFunctions("Stack", "precondition", "postcondition", "argCheck")
1250
1251 local stack = {}
1252 stack.IsEmpty = function(self)
1253 return self[1] == nil
1254 end
1255 stack.GetLength = function(self)
1256 return #self
1257 end
1258 stack.Push = function(self, value)
1259 self[#self+1] = value
1260 end
1261 precondition(stack, "Push", function(self, value)
1262 argCheck(value, 2, "string") -- only accept strings, no other values
1263 end)
1264 postcondition(stack, "Push", function(old, ret, self, value)
1265 assert(self:GetLength() == old.length+1)
1266 assert(not self:IsEmpty())
1267 end, function(old, self)
1268 old.length = self:GetLength()
1269 end)
1270 stack.Pop = function(self)
1271 local value = self[#self]
1272 self[#self] = nil
1273 return value
1274 end
1275 precondition(stack, "Pop", function(self)
1276 assert(self:GetLength() >= 1)
1277 end)
1278 postcondition(stack, "Pop", function(old, ret, self)
1279 assert(self:GetLength() == old.length-1)
1280 end, function(old, self)
1281 old.length = self:GetLength()
1282 end)
1283 stack.Peek = function(self)
1284 return self[#self]
1285 end
1286 precondition(stack, "Peek", function(self)
1287 assert(self:GetLength() >= 1)
1288 end)
1289 postcondition(stack, "Peek", function(old, ret, self)
1290 assert(self:GetLength() == old.length)
1291 end, function(old, self)
1292 old.length = self:GetLength()
1293 end)
1294
1295 local t = setmetatable({}, {__index=stack})
1296 t:Push("Alpha")
1297 t:Push("Bravo")
1298 t:Push(5) -- error, only strings
1299 assert(t:Pop() == "Bravo")
1300 assert(t:Pop() == "Alpha")
1301 t:Pop() -- error, out of values
1302 -----------------------------------------------------------------------------]]
1303 function Rock:GetContractFunctions(namespace, ...)
1304 if namespace == "precondition" or namespace == "postcondition" or namespace == "argCheck" then
1305 error(("Bad argument #2 to `GetContractFunctions'. Cannot be %q."):format(namespace), 2)
1306 end
1307 local t = newList()
1308 if enableContracts then
1309 for i = 1, select('#', ...) do
1310 local v = select(i, ...)
1311 if v == "precondition" then
1312 t[i] = precondition
1313 elseif v == "postcondition" then
1314 t[i] = postcondition
1315 elseif v == "argCheck" then
1316 t[i] = argCheck
1317 else
1318 error(("Bad argument #%d to `GetContractFunctions'. Expected %q, %q, or %q, got %q."):format(i+2, "precondition", "postcondition", "argCheck", tostring(v)))
1319 end
1320 end
1321 else
1322 for i = 1, select('#', ...) do
1323 t[i] = noop
1324 end
1325 end
1326 return unpackListAndDel(t)
1327 end
1328 end
1329
1330 --[[---------------------------------------------------------------------------
1331 Notes:
1332 * convert a revision string to a number
1333 Arguments:
1334 string - revision string
1335 Returns:
1336 string or number - the string given or the number retrieved from it.
1337 -----------------------------------------------------------------------------]]
1338 local function coerceRevisionToNumber(version)
1339 if type(version) == "string" then
1340 return tonumber(version:match("(%-?%d+)")) or version
1341 else
1342 return version
1343 end
1344 end
1345
1346 --[[---------------------------------------------------------------------------
1347 Notes:
1348 * try to enable the standalone library specified
1349 Arguments:
1350 string - name of the library.
1351 Returns:
1352 boolean - whether the library is properly enabled and loadable.
1353 -----------------------------------------------------------------------------]]
1354 local function TryToEnable(addon)
1355 local islod = IsAddOnLoadOnDemand(addon)
1356 if islod then
1357 local _, _, _, enabled = GetAddOnInfo(addon)
1358 EnableAddOn(addon)
1359 local _, _, _, _, loadable = GetAddOnInfo(addon)
1360 if not loadable and not enabled then
1361 DisableAddOn(addon)
1362 end
1363
1364 return loadable
1365 end
1366 end
1367
1368 --[[---------------------------------------------------------------------------
1369 Notes:
1370 * try to load the standalone library specified
1371 Arguments:
1372 string - name of the library.
1373 Returns:
1374 boolean - whether the library is loaded.
1375 -----------------------------------------------------------------------------]]
1376 local function TryToLoadStandalone(major)
1377 major = major:lower()
1378 if scannedLibraries[major] then
1379 return
1380 end
1381 scannedLibraries[major] = true
1382 local name, _, _, enabled, loadable, state = GetAddOnInfo(major)
1383 if state == "MISSING" or not IsAddOnLoadOnDemand(major) then
1384 -- backwards compatibility for X-AceLibrary
1385 local field = "X-AceLibrary-" .. major
1386 local loaded
1387 for i = 1, GetNumAddOns() do
1388 if GetAddOnMetadata(i, field) then
1389 name, _, _, enabled, loadable = GetAddOnInfo(i)
1390
1391 loadable = (enabled and loadable) or TryToEnable(name)
1392 if loadable then
1393 loaded = true
1394 LoadAddOn(name)
1395 end
1396 end
1397 end
1398
1399 return loaded
1400 elseif (enabled and loadable) or TryToEnable(major) then
1401 LoadAddOn(major)
1402 return true
1403 else
1404 return false
1405 end
1406 end
1407
1408 --[[---------------------------------------------------------------------------
1409 Notes:
1410 * Return the LibStub library, casing is unimportant.
1411 Arguments:
1412 string - name of the library.
1413 Returns:
1414 table or nil - library
1415 number - minor version
1416 -----------------------------------------------------------------------------]]
1417 local function GetLibStubLibrary(major)
1418 local lib, minor = LibStub:GetLibrary(major, true)
1419 if lib then
1420 return lib, minor
1421 end
1422 major = major:lower()
1423 for m, lib in LibStub:IterateLibraries() do
1424 if m:lower() == major then
1425 return LibStub:GetLibrary(m)
1426 end
1427 end
1428 return nil, nil
1429 end
1430
1431 local finishLibraryRegistration
1432 --[[---------------------------------------------------------------------------
1433 Notes:
1434 * create a new library if the version provided is not out of date.
1435 Arguments:
1436 string - name of the library.
1437 number - version of the library.
1438 Returns:
1439 library, oldLibrary
1440 * table or nil - the library with which to manipulate
1441 * table or nil - the old version of the library to upgrade from
1442 Example:
1443 local LibMonkey, oldLib = Rock:NewLibrary("LibMonkey-1.0", 50)
1444 if not LibMonkey then
1445 -- opt out now, out of date
1446 return
1447 end
1448 -----------------------------------------------------------------------------]]
1449 function Rock:NewLibrary(major, version)
1450 if type(major) ~= "string" then
1451 error(("Bad argument #2 to `NewLibrary'. Expected %q, got %q."):format("string", type(major)), 2)
1452 end
1453 if not major:match("^Lib[A-Z][A-Za-z%d%-]*%-%d+%.%d+$") then
1454 error(("Bad argument #2 to `NewLibrary'. Must match %q, got %q."):format("^Lib[A-Z][A-Za-z%d%-]*%-%d+%.%d+$", major), 2)
1455 end
1456 TryToLoadStandalone(major)
1457 version = coerceRevisionToNumber(version)
1458 if type(version) ~= "number" then
1459 error(("Bad argument #3 to `NewLibrary'. Expected %q, got %q."):format("number", type(version)), 2)
1460 end
1461 local library, oldMinor = LibStub:GetLibrary(major, true)
1462 if oldMinor and oldMinor >= version then
1463 -- in case LibStub is acting funny
1464 return nil, nil
1465 end
1466 local library, oldMinor = LibStub:NewLibrary(major, version)
1467 if not library then
1468 return nil, nil
1469 end
1470 local unitTests_major = unitTests[major]
1471 if unitTests_major then
1472 for k,v in pairs(unitTests_major) do
1473 unitTests_major[k] = nil
1474 end
1475 end
1476 for k, v in pairs(recycleData) do
1477 v[major] = nil
1478 end
1479 local mixinToObject_library = mixinToObject[library]
1480
1481 local oldLib
1482 if oldMinor then
1483 -- previous version exists
1484 local mixins = newList()
1485 for mixin, objectSet in pairs(mixinToObject) do
1486 if objectSet[library] then
1487 mixins[mixin] = true
1488 end
1489 end
1490 for mixin in pairs(mixins) do
1491 mixin:Unembed(library)
1492 end
1493 mixins = del(mixins)
1494 oldLib = newList()
1495 for k, v in pairs(library) do
1496 oldLib[k] = v
1497 library[k] = nil
1498 end
1499 setmetatable(oldLib, getmetatable(library))
1500 setmetatable(library, nil)
1501 end
1502 finishLibraryRegistration(major, version, library, figureCurrentAddon(2))
1503
1504 return library, oldLib
1505 end
1506 function finishLibraryRegistration(major, version, library, folder)
1507 library.name = major
1508
1509 libraries[major] = library
1510 pendingLibraries[library] = folder
1511 local exportedMethods_library = exportedMethods[library]
1512 if exportedMethods_library then
1513 local mixinToObject_library = mixinToObject[library]
1514 if mixinToObject_library then
1515 for object in pairs(mixinToObject_library) do
1516 for _,v in ipairs(exportedMethods_library) do
1517 object[v] = nil
1518 end
1519 end
1520 end
1521 exportedMethods[library] = del(exportedMethods_library)
1522 end
1523 if library ~= Rock then
1524 Rock:Embed(library)
1525 end
1526
1527 frame:Show()
1528 end
1529 if not oldRock then
1530 finishLibraryRegistration(MAJOR_VERSION, MINOR_VERSION, Rock, figureCurrentAddon(1))
1531 end
1532
1533 -- #NODOC
1534 local function __removeLibrary(libName)
1535 libraries[libName] = nil
1536 if LibStub.libs then
1537 LibStub.libs[libName] = nil
1538 end
1539 if LibStub.minors then
1540 LibStub.minors[libName] = nil
1541 end
1542 local lastCount
1543 repeat
1544 lastCount = collectgarbage('count')
1545 collectgarbage('collect')
1546 until lastCount == collectgarbage('count')
1547 end
1548 local function run(_,a)
1549 if a < 1/30 then
1550 collectgarbage('step')
1551 end
1552 end
1553
1554 --[[---------------------------------------------------------------------------
1555 Notes:
1556 * properly finalizes the library, essentially stating that it has loaded properly.
1557 * This will call :OnLibraryLoad("major", library) on every other library
1558 * This will also call :OnLibraryLoad("major", library) on the library provided, using every other library as the arguments.
1559 * An error will occur if this is not done before ADDON_LOADED.
1560 Arguments:
1561 string - name of the library.
1562 Example:
1563 local LibMonkey, oldLib = Rock:NewLibrary("LibMonkey-1.0", 50)
1564 if not LibMonkey then
1565 -- opt out now, out of date
1566 return
1567 end
1568 Rock:FinalizeLibrary("LibMonkey-1.0")
1569 -----------------------------------------------------------------------------]]
1570 function Rock:FinalizeLibrary(major)
1571 if type(major) ~= "string" then
1572 error(("Bad argument #2 to `FinalizeLibrary'. Expected %q, got %q."):format("string", type(major)), 2)
1573 end
1574 local library = libraries[major]
1575 if not library then
1576 error(("Bad argument #2 to `FinalizeLibrary'. %q is not a library."):format("string", major), 2)
1577 end
1578 pendingLibraries[library] = nil
1579 local library_OnLibraryLoad = library.OnLibraryLoad
1580 if library_OnLibraryLoad then
1581 for maj, lib in LibStub:IterateLibraries() do -- for all libraries
1582 if maj ~= major then
1583 local success, ret = pcall(library_OnLibraryLoad, library, maj, lib)
1584 if not success then
1585 geterrorhandler()(ret)
1586 break
1587 end
1588 end
1589 end
1590 end
1591 if finalizedLibraries[library] then
1592 return
1593 end
1594 finalizedLibraries[library] = true
1595 for maj, lib in pairs(libraries) do -- just Rock libraries
1596 if maj ~= major then
1597 local lib_OnLibraryLoad = lib.OnLibraryLoad
1598 if lib_OnLibraryLoad then
1599 local success, ret = pcall(lib_OnLibraryLoad, lib, major, library)
1600 if not success then
1601 geterrorhandler()(ret)
1602 end
1603 end
1604 end
1605 end
1606 if LibRockEvent then
1607 self:DispatchEvent("LibraryLoad", major, library)
1608 end
1609 end
1610
1611 local function manualFinalize(major, library)
1612 if libraries[major] then -- non-Rock libraries only
1613 return
1614 end
1615 if finalizedLibraries[library] then -- don't do it twice
1616 return
1617 end
1618 finalizedLibraries[library] = true
1619 for maj, lib in pairs(libraries) do -- just Rock libraries
1620 if maj ~= major then
1621 local lib_OnLibraryLoad = lib.OnLibraryLoad
1622 if lib_OnLibraryLoad then
1623 local success, ret = pcall(lib_OnLibraryLoad, lib, major, library)
1624 if not success then
1625 geterrorhandler()(ret)
1626 end
1627 end
1628 end
1629 end
1630 if LibRockEvent then
1631 Rock:DispatchEvent("LibraryLoad", major, library)
1632 end
1633 end
1634
1635 --[[---------------------------------------------------------------------------
1636 Arguments:
1637 string - name of the library.
1638 [optional] boolean - whether to not load a library if it is not found. Default: false
1639 [optional] boolean - whether to not error if a library is not found. Default: false
1640 Returns:
1641 library
1642 * table or nil - the library requested
1643 Example:
1644 local LibMonkey = Rock:GetLibrary("LibMonkey-1.0")
1645 -- or
1646 local LibMonkey = Rock("LibMonkey-1.0")
1647 -----------------------------------------------------------------------------]]
1648 function Rock:GetLibrary(major, dontLoad, dontError)
1649 if type(major) ~= "string" then
1650 error(("Bad argument #2 to `GetLibrary'. Expected %q, got %q."):format("string", type(major)), 2)
1651 end
1652 if dontLoad and dontLoad ~= true then
1653 error(("Bad argument #3 to `GetLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontLoad)), 2)
1654 end
1655 if dontError and dontError ~= true then
1656 error(("Bad argument #4 to `GetLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontError)), 2)
1657 end
1658 if not dontLoad then
1659 TryToLoadStandalone(major)
1660 end
1661
1662 local library = GetLibStubLibrary(major)
1663 if not library then
1664 if dontError then
1665 return nil
1666 end
1667 error(("Library %q not found."):format(major), 2)
1668 end
1669
1670 return library
1671 end
1672
1673 setmetatable(Rock, { __call = Rock.GetLibrary })
1674
1675 --[[---------------------------------------------------------------------------
1676 Arguments:
1677 string - name of the library.
1678 Returns:
1679 boolean - whether the library exists and is a proper mixin which can be embedded.
1680 Example:
1681 local isMixin = Rock:IsLibraryMixin("LibMonkey-1.0")
1682 -----------------------------------------------------------------------------]]
1683 function Rock:IsLibraryMixin(name)
1684 local library = self:GetLibrary(name, false, true)
1685 if not library then
1686 return false
1687 end
1688 return not not exportedMethods[library]
1689 end
1690
1691 --[[---------------------------------------------------------------------------
1692 Arguments:
1693 string - name of the library.
1694 [optional] boolean - whether to not load a library if it is not found. Default: false
1695 Returns:
1696 library
1697 * table or nil - the library requested
1698 Example:
1699 local hasLibMonkey = Rock:HasLibrary("LibMonkey-1.0")
1700 -----------------------------------------------------------------------------]]
1701 function Rock:HasLibrary(major, dontLoad)
1702 if type(major) ~= "string" then
1703 error(("Bad argument #2 to `HasLibrary'. Expected %q, got %q."):format("string", type(major)), 2)
1704 end
1705 if dontLoad and dontLoad ~= true then
1706 error(("Bad argument #3 to `HasLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontLoad)), 2)
1707 end
1708 if not dontLoad then
1709 TryToLoadStandalone(major)
1710 end
1711 return not not GetLibStubLibrary(major)
1712 end
1713
1714 --[[---------------------------------------------------------------------------
1715 Notes:
1716 * This is exported to all libraries
1717 Returns:
1718 major, minor
1719 * string - name of the library
1720 * number - version of the library
1721 Example:
1722 local major, minor = Rock:GetLibraryVersion() -- will be "LibRock-1.0", 12345
1723 local major, minor = LibMonkey:GetLibraryVersion() -- will be "LibMonkey-1.0", 50
1724 -----------------------------------------------------------------------------]]
1725 function Rock:GetLibraryVersion()
1726 if type(self) ~= "table" then
1727 return nil, nil
1728 end
1729 local major
1730 local name = self.name
1731 if name and GetLibStubLibrary(name) == self then
1732 major = name
1733 else
1734 for m, instance in LibStub:IterateLibraries() do
1735 if instance == self then
1736 major = m
1737 break
1738 end
1739 end
1740 if not major then
1741 return nil, nil
1742 end
1743 end
1744 local _, minor = GetLibStubLibrary(major)
1745 return major, minor
1746 end
1747
1748 --[[---------------------------------------------------------------------------
1749 Returns:
1750 an iterator to traverse all registered libraries.
1751 Example:
1752 for major, library in Rock:IterateLibraries() do
1753 -- do something with major and library
1754 end
1755 -----------------------------------------------------------------------------]]
1756 function Rock:IterateLibraries()
1757 return LibStub:IterateLibraries()
1758 end
1759
1760 --[[---------------------------------------------------------------------------
1761 Notes:
1762 * This is exported to all libraries
1763 * Allows you to set precisely what methods for the library to export.
1764 * This automatically turns a library into a mixin.
1765 Arguments:
1766 tuple - the list of method names to export.
1767 Example:
1768 local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50)
1769 LibMonkey.FlingPoo = function(self)
1770 return "Splat!"
1771 end
1772 LibMonkey:SetExportedMethods("FlingPoo")
1773 -- later
1774 local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0")
1775 assert(Darwin:FlingPoo() == "Splat!")
1776 -----------------------------------------------------------------------------]]
1777 function Rock:SetExportedMethods(...)
1778 if exportedMethods[self] then
1779 error("Cannot call `SetExportedMethods' more than once.", 2)
1780 end
1781 local t = newList(...)
1782 if #t == 0 then
1783 error("Must supply at least 1 method to `SetExportedMethods'.", 2)
1784 end
1785 for i,v in ipairs(t) do
1786 if type(self[v]) ~= "function" then
1787 error(("Bad argument #%d to `SetExportedMethods'. Method %q does not exist."):format(i+1, tostring(v)), 2)
1788 end
1789 end
1790 exportedMethods[self] = t
1791
1792 local mixinToObject_library = mixinToObject[self]
1793 if mixinToObject_library then
1794 for object in pairs(mixinToObject_library) do
1795 for _,method in ipairs(t) do
1796 object[method] = self[method]
1797 end
1798 end
1799 end
1800 end
1801
1802 --[[---------------------------------------------------------------------------
1803 Notes:
1804 * This is exported to all libraries
1805 * Embeds all the methods previously set to export onto a table.
1806 * This will call :OnEmbed(object) on the library if it is available.
1807 Arguments:
1808 table - the table with which to export methods onto.
1809 Returns:
1810 The table provided, after embedding.
1811 Example:
1812 local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50)
1813 LibMonkey.FlingPoo = function(self)
1814 return "Splat!"
1815 end
1816 LibMonkey:SetExportedMethods("FlingPoo")
1817 -- later
1818 local Darwin = {}
1819 Rock("LibMonkey-1.0"):Embed(Darwin)
1820 assert(Darwin:FlingPoo() == "Splat!")
1821 -----------------------------------------------------------------------------]]
1822 function Rock:Embed(object)
1823 if not exportedMethods[self] then
1824 error(("Cannot call `Embed' for library %q if `SetExportedMethods' has not been called."):format(tostring(self.name)), 2)
1825 end
1826 if type(object) ~= "table" then
1827 error(("Bad argument #2 to `Embed'. Expected %q, got %q."):format("table", type(object)), 2)
1828 end
1829
1830 for i,v in ipairs(exportedMethods[self]) do
1831 if type(self[v]) ~= "function" then
1832 error(("Problem embedding method %q from library %q. Expected %q, got %q."):format(tostring(v), better_tostring(self), "function", type(self[v])))
1833 end
1834 object[v] = self[v]
1835 end
1836
1837 if not mixinToObject[self] then
1838 -- weak because objects come and go
1839 mixinToObject[self] = setmetatable(newList(), weakKey)
1840 end
1841 if mixinToObject[self][object] then
1842 error(("Cannot embed library %q into the same object %q more than once."):format(better_tostring(self), better_tostring(object)), 2)
1843 end
1844 mixinToObject[self][object] = true
1845 if type(rawget(object, 'mixins')) == "table" then
1846 object.mixins[self] = true
1847 end
1848
1849 local self_OnEmbed = self.OnEmbed
1850 if self_OnEmbed then
1851 local success, ret = pcall(self_OnEmbed, self, object)
1852 if not success then
1853 geterrorhandler()(ret)
1854 end
1855 end
1856
1857 return object
1858 end
1859
1860 --[[---------------------------------------------------------------------------
1861 Notes:
1862 * This is exported to all libraries
1863 * Unembeds all the methods previously set to export onto a table.
1864 * This will error if the library is not embedded on the object
1865 * This will call :OnUnembed(object) on the library if it is available.
1866 Arguments:
1867 table - the table with which to export methods onto.
1868 Returns:
1869 The table provided, after embedding.
1870 Example:
1871 local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50)
1872 LibMonkey.FlingPoo = function(self)
1873 return "Splat!"
1874 end
1875 LibMonkey:SetExportedMethods("FlingPoo")
1876 -- later
1877 local Darwin = {}
1878 Rock("LibMonkey-1.0"):Embed(Darwin)
1879 assert(Darwin:FlingPoo() == "Splat!")
1880 Rock("LibMonkey-1.0"):Unembed(Darwin)
1881 assert(Darwin.FlingPoo == nil)
1882 -----------------------------------------------------------------------------]]
1883 function Rock:Unembed(object)
1884 if not exportedMethods[self] then
1885 error(("Cannot call `Unembed' for library %q if `SetExportedMethods' has not been called."):format(better_tostring(self)), 2)
1886 end
1887
1888 if not mixinToObject[self] or not mixinToObject[self][object] then
1889 error(("Cannot unembed library %q from object %q, since it is not embedded originally."):format(better_tostring(self), better_tostring(object)), 2)
1890 end
1891 local mixinToObject_self = mixinToObject[self]
1892 mixinToObject_self[object] = nil
1893 if not next(mixinToObject_self) then
1894 mixinToObject[self] = del(mixinToObject_self)
1895 end
1896
1897 local mixin_OnUnembed = self.OnUnembed
1898 if mixin_OnUnembed then
1899 local success, ret = pcall(mixin_OnUnembed, self, object)
1900 if not success then
1901 geterrorhandler()(ret)
1902 end
1903 end
1904
1905 for i,v in ipairs(exportedMethods[self]) do
1906 object[v] = nil
1907 end
1908 end
1909
1910 local function embedAce2Mixin(mixin, object)
1911 if not mixinToObject[mixin] then
1912 mixinToObject[mixin] = setmetatable(newList(), weakKey)
1913 end
1914 mixinToObject[mixin][object] = true
1915 mixin:embed(object)
1916 end
1917
1918 local function embedLibStubMixin(mixin, object)
1919 if not mixinToObject[mixin] then
1920 mixinToObject[mixin] = setmetatable(newList(), weakKey)
1921 end
1922 mixinToObject[mixin][object] = true
1923 mixin:Embed(object)
1924 end
1925
1926 --[[---------------------------------------------------------------------------
1927 Notes:
1928 * create a new addon with the specified name.
1929 Arguments:
1930 string - name of the addon.
1931 tuple - list of mixins with which to embed into this addon.
1932 Returns:
1933 addon
1934 * table - the addon with which to manipulate
1935 Example:
1936 local MyAddon = Rock:NewAddon("MyAddon", "Mixin-1.0", "OtherMixin-2.0")
1937 -----------------------------------------------------------------------------]]
1938 function Rock:NewAddon(name, ...)
1939 if type(name) ~= "string" then
1940 error(("Bad argument #2 to `NewAddon'. Expected %q, got %q"):format("string", type(name)), 2)
1941 end
1942 if name:match("^Lib[A-Z]") then
1943 error(("Bad argument #2 to `NewAddon'. Cannot start with %q, got %q."):format("Lib", name), 2)
1944 end
1945 if self == Rock and name:match("_") then
1946 error(("Bad argument #2 to `NewAddon'. Cannot contain underscores, got %q."):format(name), 2)
1947 end
1948
1949 if addons[name] then
1950 error(("Bad argument #2 to `NewAddon'. Addon %q already created."):format(name), 2)
1951 end
1952 local addon = setmetatable(newList(), addon_mt)
1953 addon.name = name
1954
1955 local mixinSet = newList()
1956
1957 for i = 1, select('#', ...) do
1958 local libName = select(i, ...)
1959 if mixinSet[libName] then
1960 error(("Bad argument #%d to `NewAddon'. %q already stated."):format(i+2, tostring(libName)), 2)
1961 end
1962 mixinSet[libName] = true
1963 TryToLoadStandalone(libName)
1964 local library = Rock:GetLibrary(libName, false, true)
1965 if not library then
1966 error(("Bad argument #%d to `NewAddon'. Library %q is not found."):format(i+2, tostring(libName)), 2)
1967 end
1968
1969 local style = 'rock'
1970
1971 if not exportedMethods[library] then
1972 local good = false
1973 if AceLibrary then
1974 local AceOO = AceLibrary:HasInstance("AceOO-2.0", false) and AceLibrary("AceOO-2.0")
1975 if AceOO.inherits(library, AceOO.Mixin) then
1976 good = true
1977 style = 'ace2'
1978 end
1979 end
1980 if not good and type(rawget(library, 'Embed')) == "function" then
1981 good = true
1982 style = 'libstub'
1983 end
1984 if not good then
1985 error(("Bad argument #%d to `NewAddon'. Library %q is not a mixin."):format(i+2, tostring(libName)), 2)
1986 end
1987 end
1988
1989 if library == Rock then
1990 error(("Bad argument #%d to `NewAddon'. Cannot use %q as a mixin."):format(i+2, tostring(libName)), 2)
1991 end
1992
1993 if style == 'rock' then
1994 library:Embed(addon)
1995 elseif style == 'ace2' then
1996 embedAce2Mixin(library, addon)
1997 elseif style == 'libstub' then
1998 embedLibStubMixin(library, addon)
1999 end
2000 end
2001
2002 mixinSet = del(mixinSet)
2003
2004 addons[name] = addon
2005 pendingAddons[#pendingAddons+1] = addon
2006 pendingAddonsEnable[#pendingAddonsEnable+1] = addon
2007 addonToFolder[addon] = figureCurrentAddon(self == Rock and 2 or 4)
2008
2009 frame:Show()
2010
2011 return addon
2012 end
2013
2014 --[[---------------------------------------------------------------------------
2015 Arguments:
2016 string - name of the addon.
2017 Returns:
2018 addon
2019 * table or nil - the addon requested
2020 Example:
2021 local MyAddon = Rock:GetAddon("MyAddon")
2022 -----------------------------------------------------------------------------]]
2023 function Rock:GetAddon(name)
2024 if type(name) ~= "string" then
2025 return nil
2026 end
2027 local addon = addons[name]
2028 if addon then
2029 return addon
2030 end
2031 name = name:lower()
2032 for k, v in pairs(addons) do
2033 if k:lower() == name then
2034 return v
2035 end
2036 end
2037 return nil
2038 end
2039
2040 --[[---------------------------------------------------------------------------
2041 Arguments:
2042 string or table - name of the addon or the addon itself.
2043 Returns:
2044 boolean - whether the addon requested exists.
2045 Example:
2046 local hasMyAddon = Rock:HasAddon("MyAddon")
2047 -- or
2048 local hasMyAddon = Rock:HasAddon(MyAddon)
2049 -----------------------------------------------------------------------------]]
2050 function Rock:HasAddon(name)
2051 if type(name) == "string" then
2052 local addon = addons[name]
2053 if addon then
2054 return true
2055 end
2056 name = name:lower()
2057 for k, v in pairs(addons) do
2058 if k:lower() == name then
2059 return true
2060 end
2061 end
2062 elseif type(name) == "table" then
2063 for k,v in pairs(addons) do
2064 if v == name then
2065 return true
2066 end
2067 end
2068 end
2069 return false
2070 end
2071
2072 --[[---------------------------------------------------------------------------
2073 Returns:
2074 an iterator to traverse all addons created with Rock.
2075 Example:
2076 for name, addon in Rock:IterateAddons() do
2077 -- do something with name and addon
2078 end
2079 -----------------------------------------------------------------------------]]
2080 function Rock:IterateAddons()
2081 return pairs(addons)
2082 end
2083
2084 --[[---------------------------------------------------------------------------
2085 Arguments:
2086 string - major version of the mixin library
2087 Returns:
2088 an iterator to traverse all objects that the given mixin has embedded into
2089 Example:
2090 local LibMonkey = Rock:NewLibrary("LibMonkey-1.0")
2091 local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0")
2092 for object in LibMonkey:IterateMixinObjects("LibMonkey-1.0") do
2093 assert(object == Darwin)
2094 end
2095 -----------------------------------------------------------------------------]]
2096 function Rock:IterateMixinObjects(mixinName)
2097 local mixin
2098 if type(mixinName) == "table" then
2099 mixin = mixinName
2100 else
2101 if type(mixinName) ~= "string" then
2102 error(("Bad argument #2 to `IterateMixinObjects'. Expected %q or %q, got %q."):format("table", "string", type(mixinName)), 2)
2103 end
2104 mixin = libraries[mixinName]
2105 end
2106 local mixinToObject_mixin = mixinToObject[mixin]
2107 if not mixinToObject_mixin then
2108 return noop
2109 end
2110 return pairs(mixinToObject_mixin)
2111 end
2112
2113 local function iter(object, mixin)
2114 mixin = next(mixinToObject, mixin)
2115 if not mixin then
2116 return nil
2117 elseif mixinToObject[mixin][object] then
2118 return mixin
2119 end
2120 return iter(object, mixin) -- try next mixin
2121 end
2122 --[[---------------------------------------------------------------------------
2123 Returns:
2124 an iterator to traverse all mixins that an object has embedded
2125 Example:
2126 local LibMonkey = Rock:NewLibrary("LibMonkey-1.0")
2127 local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0")
2128 for mixin in Rock:IterateObjectMixins(Darwin) do
2129 assert(mixin == LibMonkey)
2130 end
2131 -----------------------------------------------------------------------------]]
2132 function Rock:IterateObjectMixins(object)
2133 if type(object) ~= "table" then
2134 error(("Bad argument #2 to `IterateObjectMixins'. Expected %q, got %q."):format("table", type(object)), 2)
2135 end
2136 return iter, object, nil
2137 end
2138
2139 --[[---------------------------------------------------------------------------
2140 Arguments:
2141 table - the object to check
2142 string - the mixin to check
2143 Returns:
2144 boolean - whether the object has the given mixin embedded into it.
2145 Example:
2146 local LibMonkey = Rock:NewLibrary("LibMonkey-1.0")
2147 local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0")
2148 assert(Rock:DoesObjectUseMixin(Darwin, "LibMonkey-1.0"))
2149 -----------------------------------------------------------------------------]]
2150 function Rock:DoesObjectUseMixin(object, mixinName)
2151 if type(object) ~= "table" then
2152 error(("Bad argument #2 to `IterateObjectMixins'. Expected %q, got %q."):format("table", type(object)), 2)
2153 end
2154 local mixin
2155 if type(mixinName) == "table" then
2156 mixin = mixinName
2157 else
2158 if type(mixinName) ~= "string" then
2159 error(("Bad argument #3 to `IterateMiDoesObjectUseMixininObjects'. Expected %q or %q, got %q."):format("table", "string", type(mixinName)), 2)
2160 end
2161 mixin = libraries[mixinName]
2162 end
2163 if not mixin then
2164 return false
2165 end
2166
2167 local mixinToObject_mixin = mixinToObject[mixin]
2168 if not mixinToObject_mixin then
2169 return false
2170 end
2171 return not not mixinToObject_mixin[object]
2172 end
2173
2174 Rock.UID_NUM = oldRock and oldRock.UID_NUM or 0
2175 --[[---------------------------------------------------------------------------
2176 Notes:
2177 * 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.
2178 Returns:
2179 number - a unique number.
2180 Example:
2181 local UID = Rock:GetUID()
2182 -----------------------------------------------------------------------------]]
2183 function Rock:GetUID()
2184 local num = Rock.UID_NUM + 1
2185 Rock.UID_NUM = num
2186 return num
2187 end
2188
2189 local function unobfuscateEmail(email)
2190 return email:gsub(" AT ", "@"):gsub(" DOT ", ".")
2191 end
2192 local function fix(char)
2193 return ("%%%02x"):format(char:byte())
2194 end
2195 local function urlencode(text)
2196 return text:gsub("[^0-9A-Za-z]", fix)
2197 end
2198
2199 local url
2200 local function makeURLFrame()
2201 makeURLFrame = nil
2202 local function bumpFrameLevels(frame, amount)
2203 frame:SetFrameLevel(frame:GetFrameLevel()+amount)
2204 local children = newList(frame:GetChildren())
2205 for _,v in ipairs(children) do
2206 bumpFrameLevels(v, amount)
2207 end
2208 children = del(children)
2209 end
2210 -- some code borrowed from Prat here
2211 StaticPopupDialogs["ROCK_SHOW_URL"] = {
2212 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."],
2213 button2 = ACCEPT,
2214 hasEditBox = 1,
2215 hasWideEditBox = 1,
2216 showAlert = 1, -- HACK : it's the only way I found to make de StaticPopup have sufficient width to show WideEditBox :(
2217
2218 OnShow = function()
2219 local editBox = _G[this:GetName() .. "WideEditBox"]
2220 editBox:SetText(url)
2221 editBox:SetFocus()
2222 editBox:HighlightText(0)
2223 editBox:SetScript("OnTextChanged", function() StaticPopup_EditBoxOnTextChanged() end)
2224
2225 local button = _G[this:GetName() .. "Button2"]
2226 button:ClearAllPoints()
2227 button:SetWidth(200)
2228 button:SetPoint("CENTER", editBox, "CENTER", 0, -30)
2229
2230 _G[this:GetName() .. "AlertIcon"]:Hide() -- HACK : we hide the false AlertIcon
2231 this:SetFrameStrata("FULLSCREEN_DIALOG")
2232 bumpFrameLevels(this, 30)
2233 end,
2234 OnHide = function()
2235 local editBox = _G[this:GetName() .. "WideEditBox"]
2236 editBox:SetScript("OnTextChanged", nil)
2237 this:SetFrameStrata("DIALOG")
2238 bumpFrameLevels(this, -30)
2239 end,
2240 OnAccept = function() end,
2241 OnCancel = function() end,
2242 EditBoxOnEscapePressed = function() this:GetParent():Hide() end,
2243 EditBoxOnTextChanged = function()
2244 this:SetText(url)
2245 this:SetFocus()
2246 this:HighlightText(0)
2247 end,
2248 timeout = 0,
2249 whileDead = 1,
2250 hideOnEscape = 1
2251 }
2252 end
2253
2254 function OpenDonationFrame(self)
2255 if makeURLFrame then
2256 makeURLFrame()
2257 end
2258
2259 local donate = self.donate
2260 if type(donate) ~= "string" then
2261 donate = "Wowace"
2262 end
2263 local style, data = (":"):split(donate, 2)
2264 style = style:lower()
2265 if style ~= "website" and style ~= "paypal" then
2266 style = "wowace"
2267 end
2268 if style == "wowace" then
2269 url = "http://www.wowace.com/wiki/Donations"
2270 elseif style == "website" then
2271 url = data
2272 else -- PayPal
2273 local text = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=" .. urlencode(unobfuscateEmail(data))
2274 local name
2275 if type(self.title) == "string" then
2276 name = self.title
2277 elseif type(self.name) == "string" then
2278 name = self.name
2279 end
2280 if name == MAJOR_VERSION then
2281 name = "Rock"
2282 end
2283 if name then
2284 name = name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
2285 text = text .. "&item_name=" .. urlencode(name)
2286 end
2287 url = text
2288 end
2289
2290 StaticPopup_Show("ROCK_SHOW_URL")
2291 end
2292 function OpenIssueFrame(self)
2293 if makeURLFrame then
2294 makeURLFrame()
2295 end
2296
2297 local issueTracker = self.issueTracker
2298 if type(issueTracker) ~= "string" then
2299 return
2300 end
2301 local style, data = (":"):split(issueTracker, 2)
2302 style = style:lower()
2303 if style ~= "website" and style ~= "wowace" then
2304 return
2305 end
2306 if style == "wowace" then
2307 url = "http://jira.wowace.com/secure/CreateIssue.jspa?pid=" .. data
2308 elseif style == "website" then
2309 url = data
2310 end
2311
2312 StaticPopup_Show("ROCK_SHOW_URL")
2313 end
2314 local function donate_hidden(addon)
2315 return type(addon.donate) ~= "string"
2316 end
2317
2318 local function issue_hidden(addon)
2319 return type(addon.issueTracker) ~= "string"
2320 end
2321
2322 -- #NODOC
2323 function Rock:GetRockConfigOptions(addon)
2324 return 'active', {
2325 type = 'boolean',
2326 name = L["Enabled"],
2327 desc = L["Enable or disable this addon."],
2328 get = 'IsActive',
2329 set = 'ToggleActive',
2330 handler = addon,
2331 order = -1,
2332 }, 'donate', {
2333 type = 'execute',
2334 name = L["Give donation"],
2335 buttonText = L["Donate"],
2336 desc = L["Give a much-needed donation to the author of this addon."],
2337 func = OpenDonationFrame,
2338 hidden = donate_hidden,
2339 passValue = addon,
2340 order = -2,
2341 }, 'issue', {
2342 type = 'execute',
2343 name = L["File issue"],
2344 buttonText = L["Report"],
2345 desc = L["File a bug or request a new feature or an improvement to this addon."],
2346 func = OpenIssueFrame,
2347 hidden = issue_hidden,
2348 passValue = addon,
2349 order = -3,
2350 }
2351 end
2352
2353 local function initAddon(addon, name)
2354 name = addonToFolder[addon] or name or ""
2355 -- TOC checks
2356 if addon.title == nil then
2357 addon.title = GetAddOnMetadata(name, "Title")
2358 end
2359 if type(addon.title) == "string" then
2360 addon.title = addon.title:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):gsub("%-Rock%-$", ""):trim()
2361 end
2362 if addon.notes == nil then
2363 addon.notes = GetAddOnMetadata(name, "Notes")
2364 end
2365 if type(addon.notes) == "string" then
2366 addon.notes = addon.notes:trim()
2367 end
2368 if addon.version == nil then
2369 addon.version = GetAddOnMetadata(name, "Version")
2370 end
2371 if type(addon.version) == "string" then
2372 addon.version = addon.version:trim()
2373 end
2374 if addon.author == nil then
2375 addon.author = GetAddOnMetadata(name, "Author")
2376 end
2377 if type(addon.author) == "string" then
2378 addon.author = addon.author:trim()
2379 end
2380 if addon.credits == nil then
2381 addon.credits = GetAddOnMetadata(name, "X-Credits")
2382 end
2383 if type(addon.credits) == "string" then
2384 addon.credits = addon.credits:trim()
2385 end
2386 if addon.donate == nil then
2387 addon.donate = GetAddOnMetadata(name, "X-Donate")
2388 end
2389 if type(addon.donate) == "string" then
2390 addon.donate = addon.donate:trim()
2391 end
2392 if addon.issueTracker == nil then
2393 addon.issueTracker = GetAddOnMetadata(name, "X-IssueTracker")
2394 end
2395 if type(addon.issueTracker) == "string" then
2396 addon.issueTracker = addon.issueTracker:trim()
2397 end
2398 if addon.category == nil then
2399 addon.category = GetAddOnMetadata(name, "X-Category")
2400 end
2401 if type(addon.category) == "string" then
2402 addon.category = addon.category:trim()
2403 end
2404 if addon.email == nil then
2405 addon.email = GetAddOnMetadata(name, "X-eMail") or GetAddOnMetadata(name, "X-Email")
2406 end
2407 if type(addon.email) == "string" then
2408 addon.email = addon.email:trim()
2409 end
2410 if addon.license == nil then
2411 addon.license = GetAddOnMetadata(name, "X-License")
2412 end
2413 if type(addon.license) == "string" then
2414 addon.license = addon.license:trim()
2415 end
2416 if addon.website == nil then
2417 addon.website = GetAddOnMetadata(name, "X-Website")
2418 end
2419 if type(addon.website) == "string" then
2420 addon.website = addon.website:trim()
2421 end
2422
2423 for mixin in Rock:IterateObjectMixins(addon) do
2424 local mixin_OnEmbedInitialize = mixin.OnEmbedInitialize
2425 if mixin_OnEmbedInitialize then
2426 local success, ret = pcall(mixin_OnEmbedInitialize, mixin, addon)
2427 if not success then
2428 geterrorhandler()(ret)
2429 end
2430 end
2431 end
2432
2433 local addon_OnInitialize = addon.OnInitialize
2434 if addon_OnInitialize then
2435 local success, ret = pcall(addon_OnInitialize, addon)
2436 if not success then
2437 geterrorhandler()(ret)
2438 end
2439 end
2440
2441 if LibRockEvent then
2442 Rock:DispatchEvent("AddonInitialized", addon)
2443 end
2444 end
2445
2446
2447 local function manualEnable(addon)
2448 for i,v in ipairs(pendingAddons) do
2449 if v == addon then
2450 return
2451 end
2452 end
2453 if currentlyEnabledAddons[addon] then
2454 return false
2455 end
2456 currentlyEnabledAddons[addon] = true
2457
2458 local first = not addonsAlreadyEnabled[addon]
2459 addonsAlreadyEnabled[addon] = true
2460
2461 for mixin in Rock:IterateObjectMixins(addon) do
2462 local mixin_OnEmbedEnable = mixin.OnEmbedEnable
2463 if mixin_OnEmbedEnable then
2464 local success, ret = pcall(mixin_OnEmbedEnable, mixin, addon, first)
2465 if not success then
2466 geterrorhandler()(ret)
2467 end
2468 end
2469 end
2470 local addon_OnEnable = addon.OnEnable
2471 if addon_OnEnable then
2472 local success, ret = pcall(addon_OnEnable, addon, first)
2473 if not success then
2474 geterrorhandler()(ret)
2475 end
2476 end
2477
2478 if LibRockEvent then
2479 Rock:DispatchEvent("AddonEnabled", addon, first)
2480 end
2481
2482 return true, first
2483 end
2484
2485 local function manualDisable(addon)
2486 if not currentlyEnabledAddons[addon] then
2487 return false
2488 end
2489 currentlyEnabledAddons[addon] = nil
2490
2491 for mixin in Rock:IterateObjectMixins(addon) do
2492 local mixin_OnEmbedDisable = mixin.OnEmbedDisable
2493 if mixin_OnEmbedDisable then
2494 local success, ret = pcall(mixin_OnEmbedDisable, mixin, addon)
2495 if not success then
2496 geterrorhandler()(ret)
2497 end
2498 end
2499 end
2500 local addon_OnDisable = addon.OnDisable
2501 if addon_OnDisable then
2502 local success, ret = pcall(addon_OnDisable, addon)
2503 if not success then
2504 geterrorhandler()(ret)
2505 end
2506 end
2507
2508 if LibRockEvent then
2509 Rock:DispatchEvent("AddonDisabled", addon)
2510 end
2511 return true
2512 end
2513
2514 local function enableAddon(addon)
2515 for i,v in ipairs(pendingAddons) do
2516 if v == addon then
2517 return
2518 end
2519 end
2520 if addon_mt___index.IsActive(addon) then
2521 manualEnable(addon)
2522 end
2523 end
2524
2525 -- #NODOC
2526 -- This is used by internal Rock libraries after updating the active state.
2527 function Rock:RecheckEnabledStates()
2528 local changed = false
2529 for _,addon in pairs(addons) do
2530 local good = true
2531 for _,a in ipairs(pendingAddonsEnable) do
2532 if addon == a then
2533 good = false
2534 break
2535 end
2536 end
2537 if good then
2538 if addon_mt___index.IsActive(addon) then
2539 if manualEnable(addon) then
2540 changed = true
2541 end
2542 else
2543 if manualDisable(addon) then
2544 changed = true
2545 end
2546 end
2547 end
2548 end
2549 if changed then
2550 return self:RecheckEnabledStates()
2551 end
2552 end
2553
2554 frame:UnregisterAllEvents()
2555 frame:RegisterEvent("ADDON_LOADED")
2556 frame:RegisterEvent("PLAYER_LOGIN")
2557 local function runMainAddonLoadedChunk(name)
2558 local tmp = newList()
2559 tmp, pendingAddons = pendingAddons, tmp
2560 for i, addon in ipairs(tmp) do
2561 local folder = addonToFolder[addon]
2562 if name and folder and not foldersLoaded[folder] then
2563 for j = i, #tmp do
2564 pendingAddons[#pendingAddons+1] = tmp[j]
2565 tmp[j] = nil
2566 end
2567 break
2568 end
2569 initAddon(addon, name)
2570 end
2571
2572 if IsLoggedIn() then
2573 for i, addon in ipairs(tmp) do
2574 for j, v in ipairs(pendingAddonsEnable) do
2575 if v == addon then
2576 table_remove(pendingAddonsEnable, i)
2577 break
2578 end
2579 end
2580 enableAddon(addon)
2581 end
2582 for i, addon in ipairs(pendingAddonsEnable) do
2583 local good = true
2584 for j, v in ipairs(pendingAddons) do
2585 if v == addon then
2586 good = false
2587 break
2588 end
2589 end
2590 if not good then
2591 break
2592 end
2593 pendingAddonsEnable[i] = nil
2594 enableAddon(addon)
2595 end
2596 end
2597 tmp = del(tmp)
2598 for library, addonName in pairs(pendingLibraries) do
2599 if not name or foldersLoaded[addonName] then
2600 local success, ret = pcall(error, ("Library %q not finalized before ADDON_LOADED."):format(better_tostring(library)), 3)
2601 geterrorhandler()(ret)
2602 Rock:FinalizeLibrary((library:GetLibraryVersion()))
2603 end
2604 end
2605
2606 if isStandalone then
2607 local LibRock_1_0DB = _G.LibRock_1_0DB
2608 if type(LibRock_1_0DB) ~= "table" then
2609 LibRock_1_0DB = {}
2610 _G.LibRock_1_0DB = LibRock_1_0DB
2611 end
2612 if type(LibRock_1_0DB.unitTests) ~= "table" then
2613 LibRock_1_0DB.unitTests = {}
2614 end
2615 enableContracts = LibRock_1_0DB.contracts or false
2616 unitTestDB = LibRock_1_0DB.unitTests
2617 for namespace, data in pairs(unitTests) do
2618 if not unitTestDB[namespace] then
2619 if data then
2620 del(data)
2621 unitTests[namespace] = false
2622 end
2623 elseif data and (not name or data.addon == name) then
2624 local stats = newList()
2625 for i,v in ipairs(data) do
2626 data[i] = nil
2627
2628 local libs = newList()
2629 for k,v in pairs(libraries) do
2630 libs[k] = v
2631 end
2632
2633 local success, ret = pcall(v)
2634 if not success then
2635 geterrorhandler()(ret)
2636 stats[i] = ret
2637 else
2638 stats[i] = false
2639 end
2640
2641 for k in pairs(libraries) do
2642 if not libs[k] then
2643 __removeLibrary(k)
2644 end
2645 end
2646 libs = del(libs)
2647
2648 local lastCount
2649 repeat
2650 lastCount = collectgarbage('count')
2651 collectgarbage('collect')
2652 until lastCount == collectgarbage('count')
2653 end
2654 del(data)
2655 unitTests[namespace] = false
2656 if #stats >= 1 then
2657 local pass, fail = 0, 0
2658 for i,v in ipairs(stats) do
2659 if v then
2660 fail = fail + 1
2661 else
2662 pass = pass + 1
2663 end
2664 end
2665
2666 local color
2667 if fail == 0 then
2668 _G.DEFAULT_CHAT_FRAME:AddMessage(("|cff00ff00%s: %d unit test(s) passed."):format(namespace, pass))
2669 elseif pass > 0 then
2670 _G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s: %d unit test(s) passed, %d unit test(s) failed."):format(namespace, pass, fail))
2671 else
2672 _G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s: %d unit test(s) failed."):format(namespace, fail))
2673 end
2674 for i,v in ipairs(stats) do
2675 if v then
2676 _G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s|r"):format(tostring(v)))
2677 end
2678 end
2679 if fail > 0 then
2680 _G.DEFAULT_CHAT_FRAME:AddMessage("|cffff0000----------|r")
2681 end
2682 end
2683 stats = del(stats)
2684 end
2685 end
2686 end
2687 if isStandalone and name == MAJOR_VERSION then
2688 Rock("LibRockEvent-1.0", false, true) -- load if possible
2689 Rock("LibRockConsole-1.0", false, true) -- load if possible - I like the default chat commands
2690 Rock("LibRockComm-1.0", false, true) -- load if possible - has version checking and the like
2691 Rock("LibRockConfig-1.0", false, true) -- load if possible - LibRock-1.0 registers with it.
2692 end
2693
2694 for major, library in LibStub:IterateLibraries() do
2695 manualFinalize(major, library)
2696 end
2697
2698 if IsLoggedIn() then
2699 collectgarbage('collect')
2700 end
2701 end
2702
2703 frame:Show()
2704 frame:SetScript("OnUpdate", function(this, elapsed)
2705 -- capture all un-initialized addons.
2706 runMainAddonLoadedChunk()
2707 collectgarbage('collect')
2708 this:SetScript("OnUpdate", run)
2709 end)
2710 frame:SetScript("OnEvent", function(this, event, ...)
2711 if event == "ADDON_LOADED" then
2712 -- this creates a new table and flushes the old in case someone LoDs an addon inside ADDON_LOADED.
2713 local name = ...
2714 foldersLoaded[name] = true
2715 runMainAddonLoadedChunk(name)
2716 frame:Show()
2717 elseif event == "PLAYER_LOGIN" then
2718 for i, addon in ipairs(pendingAddonsEnable) do
2719 local good = true
2720 for _, a in ipairs(pendingAddons) do
2721 if a == addon then
2722 good = false
2723 break
2724 end
2725 end
2726 if good then
2727 pendingAddonsEnable[i] = nil
2728 enableAddon(addon)
2729 end
2730 end
2731 collectgarbage('collect')
2732 end
2733 end)
2734
2735 Rock:SetExportedMethods("SetExportedMethods", "Embed", "Unembed", "GetLibraryVersion")
2736
2737 Rock:FinalizeLibrary(MAJOR_VERSION)
2738
2739 for major, library in LibStub:IterateLibraries() do
2740 manualFinalize(major, library)
2741 end
2742
2743 Rock:AddUnitTest(MAJOR_VERSION, function()
2744 -- test recycling
2745 local newList, newDict, newSet, del = Rock:GetRecyclingFunctions(MAJOR_VERSION .. "_UnitTest", "newList", "newDict", "newSet", "del", "Debug")
2746 local t = newList("Alpha", "Bravo", "Charlie")
2747 assert(t[1] == "Alpha")
2748 assert(t[2] == "Bravo")
2749 assert(t[3] == "Charlie")
2750 t = del(t)
2751 t = newList("Alpha", "Bravo", "Charlie")
2752 -- check recycled table
2753 assert(t[1] == "Alpha")
2754 assert(t[2] == "Bravo")
2755 assert(t[3] == "Charlie")
2756 t = del(t)
2757 t = newDict("Alpha", "Bravo", "Charlie", "Delta")
2758 assert(t.Alpha == "Bravo")
2759 assert(t.Charlie == "Delta")
2760 t = del(t)
2761 t = newSet("Alpha", "Bravo", "Charlie")
2762 assert(t.Alpha)
2763 assert(t.Bravo)
2764 assert(t.Charlie)
2765 t = del(t)
2766
2767 local debug = recycleData.debugPools[MAJOR_VERSION .. "_UnitTest"]
2768 assert(debug.num == 0)
2769 t = newList()
2770 assert(debug.num == 1)
2771 t[1] = newList()
2772 assert(debug.num == 2)
2773 t[2] = newList()
2774 assert(debug.num == 3)
2775 t[1] = del(t[1])
2776 assert(debug.num == 2)
2777 t[2] = del(t[2])
2778 assert(debug.num == 1)
2779 t = del(t)
2780 assert(debug.num == 0)
2781 end)
2782
2783 Rock:AddUnitTest(MAJOR_VERSION, function()
2784 -- test :GetUID()
2785 local t = {}
2786 for i = 1, 10000 do
2787 local uid = Rock:GetUID()
2788 if t[i] then
2789 error(("UID match for iteration %d, UID %s"):format(i, uid))
2790 end
2791 t[i] = true
2792 end
2793 end)
2794
2795 Rock:AddUnitTest(MAJOR_VERSION, function()
2796 -- test basic creation and deletion
2797 assert(not LibStub:GetLibrary("LibRockFakeLib-1.0", true))
2798 assert(not Rock:HasLibrary("LibRockFakeLib-1.0"))
2799 local lib = Rock:NewLibrary("LibRockFakeLib-1.0", 1)
2800 Rock:FinalizeLibrary("LibRockFakeLib-1.0")
2801 lib = nil
2802 assert(LibStub:GetLibrary("LibRockFakeLib-1.0", true))
2803 assert(Rock:HasLibrary("LibRockFakeLib-1.0"))
2804 local good = false
2805 for _, lib in pairs(libraries) do
2806 if lib.name == "LibRockFakeLib-1.0" then
2807 good = true
2808 break
2809 end
2810 end
2811 assert(good)
2812 __removeLibrary("LibRockFakeLib-1.0")
2813 for _, lib in pairs(libraries) do
2814 assert(lib.name ~= "LibRockFakeLib-1.0")
2815 end
2816 assert(not LibStub:GetLibrary("LibRockFakeLib-1.0", true))
2817 assert(not Rock:HasLibrary("LibRockFakeLib-1.0"))
2818 end)
2819
2820 Rock:AddUnitTest(MAJOR_VERSION, function()
2821 -- test library creation and the like
2822 assert(not Rock:HasLibrary("LibRockFakeLib-1.0"))
2823 for name in Rock:IterateLibraries() do
2824 assert(name ~= "LibRockFakeLib-1.0")
2825 end
2826
2827 local myLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 1)
2828 assert(myLib)
2829 assert(myLib.name == "LibRockFakeLib-1.0")
2830 assert(not oldLib)
2831
2832 assert(myLib:GetLibraryVersion() == "LibRockFakeLib-1.0")
2833 assert(select(2, myLib:GetLibraryVersion()) == 1)
2834
2835 local good = false
2836 for name in Rock:IterateLibraries() do
2837 if name == "LibRockFakeLib-1.0" then
2838 good = true
2839 break
2840 end
2841 end
2842 assert(good)
2843 assert(Rock:HasLibrary("LibRockFakeLib-1.0"))
2844 assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib)
2845 assert(Rock("LibRockFakeLib-1.0") == myLib)
2846
2847 assert(not Rock:IsLibraryMixin("LibRockFakeLib-1.0"))
2848 function myLib:DoSomething()
2849 return "Something"
2850 end
2851 myLib:SetExportedMethods("DoSomething")
2852 assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0"))
2853 local t = {}
2854 assert(not Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0"))
2855 assert(not t.DoSomething)
2856 for mixin in Rock:IterateObjectMixins(t) do
2857 assert(false)
2858 end
2859 for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do
2860 assert(false)
2861 end
2862 myLib:Embed(t)
2863 assert(t:DoSomething() == "Something")
2864 assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0"))
2865 for mixin in Rock:IterateObjectMixins(t) do
2866 assert(mixin == myLib)
2867 end
2868 for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do
2869 assert(object == t)
2870 end
2871
2872 Rock:FinalizeLibrary("LibRockFakeLib-1.0")
2873
2874 local myNewLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 2)
2875 assert(myNewLib == myLib)
2876 assert(oldLib)
2877 assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib)
2878
2879 function myLib:DoSomething()
2880 return "Something else"
2881 end
2882 function myLib:TrySomething()
2883 return "Blah"
2884 end
2885 myLib:SetExportedMethods("DoSomething", "TrySomething")
2886 assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0"))
2887 assert(t:DoSomething() == "Something else")
2888 assert(t:TrySomething() == "Blah")
2889 assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0"))
2890 for mixin in Rock:IterateObjectMixins(t) do
2891 assert(mixin == myLib)
2892 end
2893 for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do
2894 assert(object == t)
2895 end
2896
2897 Rock:FinalizeLibrary("LibRockFakeLib-1.0")
2898
2899 local myNewLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 3)
2900 assert(myNewLib == myLib)
2901 assert(oldLib)
2902 assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib)
2903
2904 function myLib:DoSomething()
2905 return "Something"
2906 end
2907 myLib:SetExportedMethods("DoSomething")
2908 assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0"))
2909 assert(t:DoSomething() == "Something")
2910 assert(t.TrySomething == nil)
2911 assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0"))
2912 for mixin in Rock:IterateObjectMixins(t) do
2913 assert(mixin == myLib)
2914 end
2915 for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do
2916 assert(object == t)
2917 end
2918
2919 Rock:FinalizeLibrary("LibRockFakeLib-1.0")
2920
2921 assert(not Rock:NewLibrary("LibRockFakeLib-1.0", 2)) -- out of date
2922 assert(not Rock:NewLibrary("LibRockFakeLib-1.0", 3)) -- same revision
2923 end)
2924
2925 Rock:AddUnitTest(MAJOR_VERSION, function()
2926 assert(not Rock:HasAddon("RockFakeAddon"))
2927 for name in Rock:IterateAddons() do
2928 assert(name ~= "RockFakeAddon")
2929 end
2930
2931 local myAddon = Rock:NewAddon("RockFakeAddon")
2932
2933 assert(myAddon)
2934 assert(myAddon.name == "RockFakeAddon")
2935
2936 local good = false
2937 for name in Rock:IterateAddons() do
2938 if name == "RockFakeAddon" then
2939 good = true
2940 break
2941 end
2942 end
2943 assert(good)
2944 assert(Rock:HasAddon("RockFakeAddon"))
2945 assert(Rock:GetAddon("RockFakeAddon") == myAddon)
2946 end)
2947
2948 Rock:AddUnitTest(MAJOR_VERSION, function()
2949 -- test :OnLibraryLoad
2950 local lib = Rock:NewLibrary("LibRockFakeLib-1.0", 1)
2951 local triggered = false
2952 function lib:OnLibraryLoad(major, instance)
2953 if major == "LibRockFakeLib-2.0" then
2954 triggered = true
2955 end
2956 end
2957 Rock:FinalizeLibrary("LibRockFakeLib-1.0")
2958
2959 local lib = Rock:NewLibrary("LibRockFakeLib-2.0", 1)
2960 assert(not triggered)
2961 Rock:FinalizeLibrary("LibRockFakeLib-2.0")
2962 assert(triggered)
2963 triggered = false
2964 local lib = Rock:NewLibrary("LibRockFakeLib-2.0", 2)
2965 assert(not triggered)
2966 Rock:FinalizeLibrary("LibRockFakeLib-2.0")
2967 assert(not triggered)
2968 end)