mirror of
https://github.com/parnic/ice-hud.git
synced 2025-06-16 06:40:13 -05:00
Re-enable dev-only profile export/import
Exporting works pretty well, but importing is rough (mostly because error messaging isn't quite in place, and it's not obvious that you have to close the window to execute the import). I found a generic json serializer, decided to adapt it to WoW, and it seems to work. But anyway, I saw this code sitting around and figured it wouldn't take too much work to get it in working order. I was mostly right.
This commit is contained in:
39
IceHUD.lua
39
IceHUD.lua
@ -7,8 +7,6 @@ local SML = LibStub("LibSharedMedia-3.0")
|
||||
local ACR = LibStub("AceConfigRegistry-3.0")
|
||||
local ConfigDialog = LibStub("AceConfigDialog-3.0")
|
||||
local icon = LibStub("LibDBIcon-1.0", true)
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
local AceSerializer = LibStub("AceSerializer-3.0", 1)
|
||||
|
||||
local pendingModuleLoads = {}
|
||||
local bReadyToRegisterModules = false
|
||||
@ -92,6 +90,43 @@ end
|
||||
|
||||
IceHUD.deepcopy = deepcopy
|
||||
|
||||
function IceHUD:removeDefaults(db, defaults, blocker)
|
||||
-- remove all metatables from the db, so we don't accidentally create new sub-tables through them
|
||||
setmetatable(db, nil)
|
||||
-- loop through the defaults and remove their content
|
||||
for k,v in pairs(defaults) do
|
||||
if type(v) == "table" and type(db[k]) == "table" then
|
||||
-- if a blocker was set, dive into it, to allow multi-level defaults
|
||||
self:removeDefaults(db[k], v, blocker and blocker[k])
|
||||
if next(db[k]) == nil then
|
||||
db[k] = nil
|
||||
end
|
||||
else
|
||||
-- check if the current value matches the default, and that its not blocked by another defaults table
|
||||
if db[k] == defaults[k] and (not blocker or blocker[k] == nil) then
|
||||
db[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function IceHUD:populateDefaults(db, defaults, blocker)
|
||||
-- remove all metatables from the db, so we don't accidentally create new sub-tables through them
|
||||
setmetatable(db, nil)
|
||||
-- loop through the defaults and add their content
|
||||
for k,v in pairs(defaults) do
|
||||
if type(v) == "table" and type(db[k]) == "table" then
|
||||
-- if a blocker was set, dive into it, to allow multi-level defaults
|
||||
self:populateDefaults(db[k], v, blocker and blocker[k])
|
||||
else
|
||||
-- check if the current value matches the default, and that its not blocked by another defaults table
|
||||
if db[k] == nil then
|
||||
db[k] = defaults[k]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
IceHUD.Location = "Interface\\AddOns\\IceHUD"
|
||||
|
||||
StaticPopupDialogs["ICEHUD_CUSTOM_BAR_CREATED"] =
|
||||
|
@ -100,5 +100,8 @@ modules\ArcaneCharges.lua
|
||||
modules\RollTheBones.lua
|
||||
|
||||
#@do-not-package@
|
||||
IceHUD_Options\Json.lua
|
||||
IceHUD_Options\JsonDecode.lua
|
||||
IceHUD_Options\JsonEncode.lua
|
||||
IceHUD_Options\Options.lua
|
||||
#@end-do-not-package@
|
||||
|
@ -11,4 +11,7 @@
|
||||
## Dependencies: IceHUD
|
||||
## LoadOnDemand: 1
|
||||
|
||||
Json.lua
|
||||
JsonDecode.lua
|
||||
JsonEncode.lua
|
||||
Options.lua
|
||||
|
1
IceHUD_Options/Json.lua
Normal file
1
IceHUD_Options/Json.lua
Normal file
@ -0,0 +1 @@
|
||||
IceHUD.json = {}
|
241
IceHUD_Options/JsonDecode.lua
Normal file
241
IceHUD_Options/JsonDecode.lua
Normal file
@ -0,0 +1,241 @@
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[select(i, ...)] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
["true"] = true,
|
||||
["false"] = false,
|
||||
["null"] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
|
||||
return string.format("%s at line %d col %d", msg, line_count, col_count)
|
||||
end
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
return "", string.format("invalid unicode codepoint '%x'", n)
|
||||
end
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber(s:sub(1, 4), 16)
|
||||
local n2 = tonumber(s:sub(7, 10), 16)
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
local function parse_string(str, i)
|
||||
local res = ""
|
||||
local j = i + 1
|
||||
local k = j
|
||||
|
||||
while j <= #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
return str, j, decode_error(str, j, "control character in string")
|
||||
|
||||
elseif x == 92 then -- `\`: Escape
|
||||
res = res .. str:sub(k, j - 1)
|
||||
j = j + 1
|
||||
local c = str:sub(j, j)
|
||||
if c == "u" then
|
||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||
or str:match("^%x%x%x%x", j + 1)
|
||||
or false
|
||||
if hex == false then
|
||||
return str, j-1, decode_error(str, j - 1, "invalid unicode escape in string")
|
||||
end
|
||||
res = res .. parse_unicode_escape(hex)
|
||||
j = j + #hex
|
||||
else
|
||||
if not escape_chars[c] then
|
||||
return str, j-1, decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
res = res .. escape_char_map_inv[c]
|
||||
end
|
||||
k = j + 1
|
||||
|
||||
elseif x == 34 then -- `"`: End of string
|
||||
res = res .. str:sub(k, j - 1)
|
||||
return res, j + 1
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
return str, i, decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
return -1, -1, decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
return false, -1, decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then return nil, -1, decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
return nil, -1, decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
return nil, -1, decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then return nil, -1, decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
local char_func_map = {
|
||||
['"'] = parse_string,
|
||||
["0"] = parse_number,
|
||||
["1"] = parse_number,
|
||||
["2"] = parse_number,
|
||||
["3"] = parse_number,
|
||||
["4"] = parse_number,
|
||||
["5"] = parse_number,
|
||||
["6"] = parse_number,
|
||||
["7"] = parse_number,
|
||||
["8"] = parse_number,
|
||||
["9"] = parse_number,
|
||||
["-"] = parse_number,
|
||||
["t"] = parse_literal,
|
||||
["f"] = parse_literal,
|
||||
["n"] = parse_literal,
|
||||
["["] = parse_array,
|
||||
["{"] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
return false, -1, decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function IceHUD.json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
return nil, "expected argument of type string, got " .. type(str)
|
||||
end
|
||||
local res, idx, err = parse(str, next_char(str, 1, space_chars, true))
|
||||
if err ~= nil then
|
||||
return nil, err
|
||||
end
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
return nil, decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res, nil
|
||||
end
|
100
IceHUD_Options/JsonEncode.lua
Normal file
100
IceHUD_Options/JsonEncode.lua
Normal file
@ -0,0 +1,100 @@
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
["\\"] = "\\",
|
||||
["\""] = "\"",
|
||||
["\b"] = "b",
|
||||
["\f"] = "f",
|
||||
["\n"] = "n",
|
||||
["\r"] = "r",
|
||||
["\t"] = "t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { ["/"] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||
end
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
local type_func_map = {
|
||||
["nil"] = encode_nil,
|
||||
["table"] = encode_table,
|
||||
["string"] = encode_string,
|
||||
["number"] = encode_number,
|
||||
["boolean"] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
function IceHUD.json.encode(val)
|
||||
return (encode(val))
|
||||
end
|
@ -1,6 +1,8 @@
|
||||
local LibDualSpec = LibStub('LibDualSpec-1.0', true)
|
||||
local L = LibStub("AceLocale-3.0"):GetLocale("IceHUD", false)
|
||||
local icon = LibStub("LibDBIcon-1.0", true)
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
local AceSerializer = LibStub("AceSerializer-3.0", 1)
|
||||
local lastCustomModule = "Bar"
|
||||
|
||||
IceHUD_Options = {}
|
||||
@ -757,6 +759,9 @@ function IceHUD_Options:OnLoad()
|
||||
self:GenerateModuleOptions(true)
|
||||
self.options.args.colors.args = IceHUD.IceCore:GetColorOptions()
|
||||
self.options.args.profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(IceHUD.db)
|
||||
--@debug@
|
||||
IceHUD_Options:SetupProfileImportButtons()
|
||||
--@end-debug@
|
||||
|
||||
-- Add dual-spec support
|
||||
if IceHUD.db ~= nil and LibDualSpec then
|
||||
@ -787,14 +792,14 @@ function IceHUD_Options:SetupProfileImportButtons()
|
||||
editbox:SetLabel("Profile")
|
||||
editbox:SetFullWidth(true)
|
||||
editbox:SetFullHeight(true)
|
||||
local profileTable = deepcopy(IceHUD.db.profile)
|
||||
local profileTable = IceHUD.deepcopy(IceHUD.db.profile)
|
||||
IceHUD:removeDefaults(profileTable, IceHUD.IceCore.defaults.profile)
|
||||
editbox:SetText(IceHUD:Serialize(profileTable))
|
||||
editbox:SetText(IceHUD.json.encode(profileTable))
|
||||
editbox:DisableButton(true)
|
||||
frame:AddChild(editbox)
|
||||
end,
|
||||
hidden =
|
||||
-- hello, snooper! this feature doesn't actually work yet, so enabling it won't help you much :)
|
||||
-- hello, snooper! exporting works well enough, but importing is very rough, so enable this at your own peril
|
||||
--[===[@non-debug@
|
||||
true
|
||||
--@end-non-debug@]===]
|
||||
@ -803,7 +808,7 @@ function IceHUD_Options:SetupProfileImportButtons()
|
||||
--@end-debug@
|
||||
,
|
||||
disabled =
|
||||
-- hello, snooper! this feature doesn't actually work yet, so enabling it won't help you much :)
|
||||
-- hello, snooper! exporting works well enough, but importing is very rough, so enable this at your own peril
|
||||
--[===[@non-debug@
|
||||
true
|
||||
--@end-non-debug@]===]
|
||||
@ -824,11 +829,14 @@ function IceHUD_Options:SetupProfileImportButtons()
|
||||
frame:SetStatusText("Exported profile details")
|
||||
frame:SetLayout("Flow")
|
||||
frame:SetCallback("OnClose", function(widget)
|
||||
local success, newTable = IceHUD:Deserialize(widget.children[1]:GetText())
|
||||
if success then
|
||||
local newTable, err = IceHUD.json.decode(widget.children[1]:GetText())
|
||||
if err ~= nil then
|
||||
print("failed to import profile: "..err)
|
||||
else
|
||||
print("importing profile")
|
||||
IceHUD:PreProfileChanged()
|
||||
IceHUD:populateDefaults(newTable, IceHUD.IceCore.defaults.profile)
|
||||
IceHUD.db.profile = deepcopy(newTable)
|
||||
IceHUD.db.profile = IceHUD.deepcopy(newTable)
|
||||
IceHUD:PostProfileChanged()
|
||||
end
|
||||
AceGUI:Release(widget)
|
||||
@ -841,7 +849,7 @@ function IceHUD_Options:SetupProfileImportButtons()
|
||||
frame:AddChild(editbox)
|
||||
end,
|
||||
hidden =
|
||||
-- hello, snooper! this feature doesn't actually work yet, so enabling it won't help you much :)
|
||||
-- hello, snooper! this feature is really rough, so enable it at your own peril
|
||||
--[===[@non-debug@
|
||||
true
|
||||
--@end-non-debug@]===]
|
||||
@ -850,7 +858,7 @@ function IceHUD_Options:SetupProfileImportButtons()
|
||||
--@end-debug@
|
||||
,
|
||||
disabled =
|
||||
-- hello, snooper! this feature doesn't actually work yet, so enabling it won't help you much :)
|
||||
-- hello, snooper! this feature is really rough, so enable it at your own peril
|
||||
--[===[@non-debug@
|
||||
true
|
||||
--@end-non-debug@]===]
|
||||
@ -862,7 +870,3 @@ function IceHUD_Options:SetupProfileImportButtons()
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
--@debug@
|
||||
IceHUD_Options:SetupProfileImportButtons()
|
||||
--@end-debug@
|
||||
|
37
IceHUD_Options/TablePrint.lua
Normal file
37
IceHUD_Options/TablePrint.lua
Normal file
@ -0,0 +1,37 @@
|
||||
local function table_print(tt, indent, done)
|
||||
done = done or {}
|
||||
indent = indent or 0
|
||||
if type(tt) == "table" then
|
||||
local sb = {}
|
||||
for key, value in pairs(tt) do
|
||||
table.insert(sb, string.rep(" ", indent)) -- indent it
|
||||
if type(value) == "table" and not done[value] then
|
||||
done[value] = true
|
||||
table.insert(sb, key .. " = {\n");
|
||||
table.insert(sb, table_print(value, indent + 2, done))
|
||||
table.insert(sb, string.rep(" ", indent)) -- indent it
|
||||
table.insert(sb, "}\n");
|
||||
elseif "number" == type(key) then
|
||||
table.insert(sb, string.format("\"%s\"\n", tostring(value)))
|
||||
else
|
||||
table.insert(sb, string.format(
|
||||
"%s = \"%s\"\n", tostring(key), tostring(value)))
|
||||
end
|
||||
end
|
||||
return table.concat(sb)
|
||||
else
|
||||
return tt .. "\n"
|
||||
end
|
||||
end
|
||||
|
||||
local function to_string(tbl)
|
||||
if "nil" == type(tbl) then
|
||||
return tostring(nil)
|
||||
elseif "table" == type(tbl) then
|
||||
return table_print(tbl)
|
||||
elseif "string" == type(tbl) then
|
||||
return tbl
|
||||
else
|
||||
return tostring(tbl)
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user