forked from Haleth/Aurora
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathconfig.lua
More file actions
491 lines (412 loc) · 15.1 KB
/
config.lua
File metadata and controls
491 lines (412 loc) · 15.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
local _, private = ...
-- [[ Lua Globals ]]
-- luacheck: globals next type pairs _G
-- Configuration Management Module
-- Handles configuration data model, defaults, validation, sanitization, and migration
local Config = {}
private.Config = Config
-- Configuration defaults with all themeable options
Config.defaults = {
acknowledgedSplashScreen = false,
-- Component toggles
bags = true,
banks = true,
chat = true,
loot = true,
mainmenubar = false,
fonts = true,
tooltips = true,
chatBubbles = true,
chatBubbleNames = true,
-- Visual customization
buttonsHaveGradient = true,
customHighlight = {
enabled = false,
r = 0.243,
g = 0.570,
b = 1
},
alpha = 0.5,
-- System flags
hasAnalytics = true,
customClassColors = {},
-- GC tuning mode: "smooth" (aggressive incremental), "default" (Lua defaults), "combat" (pause in combat)
gcMode = "smooth",
}
-- Deprecated settings mapping for migration
-- Maps old setting names to new setting names or migration functions
Config.deprecatedSettings = {
useButtonGradientColour = "buttonsHaveGradient",
enableFont = "fonts",
customColour = function(value, config)
config.customHighlight = value
return true
end,
useCustomColour = function(value, config)
if config.customHighlight then
config.customHighlight.enabled = value
end
return true
end,
}
-- Validation rules for configuration values
Config.validationRules = {
-- Boolean settings
acknowledgedSplashScreen = "boolean",
bags = "boolean",
banks = "boolean",
chat = "boolean",
loot = "boolean",
mainmenubar = "boolean",
fonts = "boolean",
tooltips = "boolean",
chatBubbles = "boolean",
chatBubbleNames = "boolean",
buttonsHaveGradient = "boolean",
hasAnalytics = "boolean",
-- Number settings with ranges
alpha = {type = "number", min = 0, max = 1},
-- Complex object validation
customHighlight = function(value)
if type(value) ~= "table" then
return false, "customHighlight must be a table"
end
if type(value.enabled) ~= "boolean" then
return false, "customHighlight.enabled must be boolean"
end
if type(value.r) ~= "number" or value.r < 0 or value.r > 1 then
return false, "customHighlight.r must be number between 0 and 1"
end
if type(value.g) ~= "number" or value.g < 0 or value.g > 1 then
return false, "customHighlight.g must be number between 0 and 1"
end
if type(value.b) ~= "number" or value.b < 0 or value.b > 1 then
return false, "customHighlight.b must be number between 0 and 1"
end
return true
end,
customClassColors = function(value)
if type(value) ~= "table" then
return false, "customClassColors must be a table"
end
return true
end,
gcMode = function(value)
if type(value) ~= "string" then
return false, "gcMode must be a string"
end
local valid = { smooth = true, default = true, combat = true }
if not valid[value] then
return false, "gcMode must be 'smooth', 'default', or 'combat'"
end
return true
end,
}
-- Validate a single configuration value
-- @param key string The configuration key
-- @param value any The value to validate
-- @return boolean success Whether validation passed
-- @return string error Optional user-friendly error message
function Config.validateValue(key, value)
local rule = Config.validationRules[key]
if not rule then
-- No validation rule means any value is acceptable
return true
end
if type(rule) == "string" then
-- Simple type check
if type(value) ~= rule then
return false, ("Setting '%s' must be a %s, but got %s"):format(key, rule, type(value))
end
return true
elseif type(rule) == "table" then
-- Complex validation with constraints
if type(value) ~= rule.type then
return false, ("Setting '%s' must be a %s, but got %s"):format(key, rule.type, type(value))
end
if rule.min and value < rule.min then
return false, ("Setting '%s' value %.2f is below minimum %.2f"):format(key, value, rule.min)
end
if rule.max and value > rule.max then
return false, ("Setting '%s' value %.2f is above maximum %.2f"):format(key, value, rule.max)
end
return true
elseif type(rule) == "function" then
-- Custom validation function
return rule(value)
end
return true
end
-- Sanitize a configuration value to ensure it's valid
-- @param key string The configuration key
-- @param value any The value to sanitize
-- @return any The sanitized value (or default if invalid)
function Config.sanitizeValue(key, value)
local valid, err = Config.validateValue(key, value)
if not valid then
private.debug("Config", "Invalid value for", key, ":", err, "- using default")
return Config.defaults[key]
end
return value
end
-- Migrate deprecated settings to current format
-- @param config table The configuration table to migrate
-- @return boolean changed Whether any migrations were performed
function Config.migrateDeprecatedSettings(config)
local changed = false
for oldKey, migration in pairs(Config.deprecatedSettings) do
if config[oldKey] ~= nil then
if type(migration) == "string" then
-- Simple key rename
config[migration] = config[oldKey]
config[oldKey] = nil
changed = true
private.debug("Config", "Migrated", oldKey, "to", migration)
elseif type(migration) == "function" then
-- Custom migration function
if migration(config[oldKey], config) then
config[oldKey] = nil
changed = true
private.debug("Config", "Migrated", oldKey, "using custom function")
end
end
end
end
return changed
end
-- Remove settings that are no longer valid
-- @param config table The configuration table to clean
-- @return boolean changed Whether any settings were removed
function Config.removeInvalidSettings(config)
local changed = false
for key in pairs(config) do
if Config.defaults[key] == nil then
config[key] = nil
changed = true
private.debug("Config", "Removed invalid setting:", key)
end
end
return changed
end
-- Deep copy a table value
-- @param value any The value to copy
-- @return any The copied value
local function deepCopy(value)
if type(value) ~= "table" then
return value
end
local copy = {}
for k, v in pairs(value) do
copy[k] = deepCopy(v)
end
return copy
end
-- Apply default values for missing configuration keys
-- @param config table The configuration table to populate
-- @return boolean changed Whether any defaults were applied
function Config.applyDefaults(config)
local changed = false
for key, defaultValue in pairs(Config.defaults) do
if config[key] == nil then
config[key] = deepCopy(defaultValue)
changed = true
private.debug("Config", "Applied default for:", key)
end
end
return changed
end
-- Validate entire configuration object
-- @param config table The configuration to validate
-- @return boolean valid Whether the configuration is valid
-- @return table errors List of validation errors
function Config.validateConfig(config)
local errors = {}
for key, value in pairs(config) do
local valid, err = Config.validateValue(key, value)
if not valid then
errors[#errors + 1] = {key = key, error = err}
end
end
return #errors == 0, errors
end
-- Sanitize entire configuration object
-- @param config table The configuration to sanitize
-- @return table The sanitized configuration
function Config.sanitizeConfig(config)
local sanitized = {}
for key, value in pairs(config) do
sanitized[key] = Config.sanitizeValue(key, value)
end
return sanitized
end
-- Settings Persistence Layer
-- Handles SavedVariables integration, loading, saving, and recovery
-- Initialize configuration from SavedVariables
-- @param wago table Optional WagoAnalytics instance for tracking
-- @return table The initialized configuration
function Config.load(wago)
-- Initialize or get existing SavedVariables
_G.AuroraConfig = _G.AuroraConfig or {}
local config = _G.AuroraConfig
-- Step 1: Migrate deprecated settings
local migrated = Config.migrateDeprecatedSettings(config)
-- Step 2: Remove invalid/unknown settings
local cleaned = Config.removeInvalidSettings(config)
-- Step 3: Track analytics for existing settings (before applying defaults)
if wago and config.hasAnalytics == nil then
for key, value in pairs(config) do
if key ~= "acknowledgedSplashScreen" then
if key == "customHighlight" then
wago:Switch(key, value.enabled)
elseif key == "alpha" then
wago:SetCounter(key, value)
else
wago:Switch(key, value)
end
end
end
end
-- Step 4: Apply defaults for missing keys
local defaultsApplied = Config.applyDefaults(config)
-- Step 5: Validate and sanitize all values
local valid, errors = Config.validateConfig(config)
if not valid then
private.debug("Config", "Configuration validation errors:")
for _, err in pairs(errors) do
private.debug("Config", " -", err.key, ":", err.error)
end
-- Sanitize invalid values
for _, err in pairs(errors) do
config[err.key] = Config.sanitizeValue(err.key, config[err.key])
end
end
if migrated or cleaned or defaultsApplied then
private.debug("Config", "Configuration updated during load")
end
return config
end
-- Save configuration to SavedVariables
-- @param config table The configuration to save (optional, uses global if not provided)
function Config.save(config)
config = config or _G.AuroraConfig
if not config then
private.debug("Config", "No configuration to save")
return
end
-- Validate before saving
local valid, errors = Config.validateConfig(config)
if not valid then
private.debug("Config", "Cannot save invalid configuration:")
for _, err in pairs(errors) do
private.debug("Config", " -", err.key, ":", err.error)
end
return false
end
-- SavedVariables are automatically persisted by WoW
-- We just need to ensure the global reference is correct
_G.AuroraConfig = config
private.debug("Config", "Configuration saved")
return true
end
-- Reset configuration to defaults
-- @param preserveAcknowledgment boolean Whether to preserve splash screen acknowledgment
-- @return table The reset configuration
function Config.reset(preserveAcknowledgment)
local config = _G.AuroraConfig or {}
-- Preserve splash screen acknowledgment if requested
local acknowledged = preserveAcknowledgment and config.acknowledgedSplashScreen
-- Clear all settings
for key in pairs(config) do
config[key] = nil
end
-- Apply all defaults
for key, value in pairs(Config.defaults) do
config[key] = deepCopy(value)
end
-- Restore acknowledgment if needed
if acknowledged then
config.acknowledgedSplashScreen = true
end
_G.AuroraConfig = config
private.debug("Config", "Configuration reset to defaults")
return config
end
-- Get a configuration value with fallback to default
-- @param key string The configuration key
-- @param config table Optional config table (uses global if not provided)
-- @return any The configuration value
function Config.get(key, config)
config = config or _G.AuroraConfig
if config and config[key] ~= nil then
return config[key]
end
return Config.defaults[key]
end
-- Set a configuration value with validation
-- @param key string The configuration key
-- @param value any The value to set
-- @param config table Optional config table (uses global if not provided)
-- @return boolean success Whether the value was set successfully
-- @return string error Optional error message if failed
function Config.set(key, value, config)
config = config or _G.AuroraConfig
if not config then
return false, "No configuration available"
end
-- Validate the value
local valid, err = Config.validateValue(key, value)
if not valid then
return false, err
end
-- Set the value
config[key] = value
-- Dispatch configuration change event if integration is available
if private.Integration and private.Integration.DispatchEvent then
private.Integration.DispatchEvent("CONFIG_CHANGED", key, value)
end
private.debug("Config", "Set", key, "to", value)
return true
end
-- Check if configuration needs recovery (corrupted or missing)
-- @param config table The configuration to check
-- @return boolean needsRecovery Whether recovery is needed
-- @return string reason The reason recovery is needed
function Config.needsRecovery(config)
if not config then
return true, "Configuration is nil"
end
if type(config) ~= "table" then
return true, "Configuration is not a table"
end
-- Check if critical settings are present
local criticalSettings = {"bags", "banks", "chat", "tooltips", "alpha"}
for _, key in pairs(criticalSettings) do
if config[key] == nil then
return true, "Missing critical setting: " .. key
end
end
return false, nil
end
-- Recover configuration from corruption
-- @return table The recovered configuration
function Config.recover()
private.debug("Config", "Attempting configuration recovery")
local oldConfig = _G.AuroraConfig
local config = {}
-- Try to salvage valid settings from old config
if type(oldConfig) == "table" then
for key, value in pairs(oldConfig) do
if Config.defaults[key] ~= nil then
local valid = Config.validateValue(key, value)
if valid then
config[key] = value
end
end
end
end
-- Apply defaults for missing keys
Config.applyDefaults(config)
_G.AuroraConfig = config
private.debug("Config", "Configuration recovered")
return config
end