From cc266833281c1c5eb1aeb9dcf88a872c291345b7 Mon Sep 17 00:00:00 2001 From: Lothar Buchholz Date: Sat, 30 May 2020 01:51:32 +0200 Subject: [PATCH] Version 0.4.0 - restructured files - extract functions and color codes - filter channels --- CHANGELOG.md | 8 +- Grichelde.lua | 393 +++------------------------------ grichelde.tga => Grichelde.tga | Bin Grichelde.toc | 5 +- GricheldeChat.lua | 245 ++++++++++++++++++++ GricheldeConstants.lua | 94 ++++++++ GricheldeOptions.lua | 110 +++------ GricheldeUtils.lua | 103 +++++++++ localisation/deDE.lua | 16 +- localisation/enUS.lua | 16 +- 10 files changed, 535 insertions(+), 455 deletions(-) rename grichelde.tga => Grichelde.tga (100%) create mode 100644 GricheldeChat.lua create mode 100644 GricheldeConstants.lua create mode 100644 GricheldeUtils.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index b654a84..f881633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - case sensitivity - consolidate consecutive matches -## Version 0.3.1 - 2020-05-28 +## Version 0.5.0 - 2020-05-31 ### Added - add replacements via options UI - handle replacement via slash command +## Version 0.4.0 - 2020-05-30 +### Added +- restructured files +- extract functions and color codes +- filter channels + ## Version 0.3.0 - 2020-05-27 ### Fixed - fixed DB storange and debug printing diff --git a/Grichelde.lua b/Grichelde.lua index f497047..63cdd18 100644 --- a/Grichelde.lua +++ b/Grichelde.lua @@ -14,32 +14,13 @@ limitations under the License. --------------------------------------------------------------------------]] -- --- upvalues -local _G = _G - --- faster function lookups by mapping to local refs -local string_find = _G.string.find -local string_gsub = _G.string.gsub -local string_len = _G.string.len -local string_rep = _G.string.rep -local string_sub = _G.string.sub -local string_fmt = _G.string.format -local strtrim = _G.strtrim -local strmatch = _G.strmatch -local tostring = _G.tostring -local tInsert = _G.table.insert -local tContains = _G.tContains -local unpack = _G.unpack -local pairs = _G.pairs -local ipairs = _G.ipairs --- colors: -local PREFIX_COLOR_CODE = "|c00FFAA00" - --- initialize addon +-- read namespace from global env local AddonName, AddonTable = ... +local _G = _G -local Grichelde = LibStub("AceAddon-3.0"):NewAddon(AddonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0") +-- initialize addon +local Grichelde = LibStub("AceAddon-3.0"):NewAddon(AddonTable, AddonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0") Grichelde.L = LibStub("AceLocale-3.0"):GetLocale("Grichelde", true) Grichelde.version = GetAddOnMetadata(AddonName, "Version") Grichelde.build = GetAddOnMetadata(AddonName, "X-Build") or "Experimental" @@ -50,12 +31,6 @@ Grichelde.debug = false -- publish to global env _G.Grichelde = Grichelde -Grichelde.config = { - enabled = true, - channels = { "SAY", "EMOTE", "YELL", "PARTY", "GUILD" }, - slashCommands = { "/s", "/say", "/e", "/em", "/me", "/emote", "/y", "/yell", "/sh", "/shout", "/p", "/party", "/pl", "/partyleader", "/g", "/gc", "/guild", "/o", "/osay", "/officer", "/raid", "/rsay", "/rl", "/raidleader", "/rw", "/raidwarning", "/i", "/instance", "/bg", "/battleground", "/w", "/whisper", "/t", "/tell", "/send", "/r", "/reply" } -} - -- Ace3 callbacks function Grichelde:OnInitialize() -- Build Interface Options window @@ -68,7 +43,6 @@ function Grichelde:OnInitialize() end function Grichelde:OnEnable() - -- Hook in before message is sent to replace all character occurrences where replacements have been defined in the options self:RawHook("SendChatMessage", true) @@ -84,30 +58,47 @@ function Grichelde:OnDisable() self:Unhook("SendChatMessage") end +--- register 'grichelde' and 'gri' slash commands +function Grichelde:SetupSlashCommands() + local function HandleSlashCommand(input) + -- Show the GUI if no input is supplied, otherwise handle the chat input. + if not input or input:trim() == "" then + LibStub("AceConfigDialog-3.0"):Open(self.name) + else + -- handle slash ourselves + self:Print(self.L.AddonName .. " Version " .. self.version) + self:Print("Handle slash command: " .. input) + end + end + + self:RegisterChatCommand("grichelde", HandleSlashCommand) + self:RegisterChatCommand("gri", HandleSlashCommand) +end + --- @param event string --- @param addonName string function Grichelde:HookIntoForOtherChatAddons(event, addonName) if event == "ADDON_LOADED" then if addonName == "WIM" then - WIM.RegisterWidgetTrigger("msg_box", "whisper,chat,w2w", "OnEnterPressed", Grichelde.EditBox_OnEnterPressed) + _G.WIM.RegisterWidgetTrigger("msg_box", "whisper,chat,w2w", "OnEnterPressed", Grichelde.EditBox_OnEnterPressed) -- If available use the WIM API - if (WIM.RegisterPreSendFilterText) then -- avoid error if WIM not up to date. - WIM.RegisterPreSendFilterText(function(text) - return Grichelde:CheckAndReplace(text) + if (_G.WIM.RegisterPreSendFilterText) then -- avoid error if WIM not up to date. + _G.WIM.RegisterPreSendFilterText(function(text) + return self:CheckAndReplace(text) end) else -- WIM sends its chat messages via the API ChatThrottleLib, which itself hooks the default SendChatMessage api -- many times before Grichelde will. ChatThrottleLib might potentially load before Grichelde, so we just hook -- into ChatThrottleLib to be on the safe side. - if (ChatThrottleLib) then - Grichelde.hooks["ChatThrottleLib"] = ChatThrottleLib.SendChatMessage + if (_G.ChatThrottleLib) then + self.hooks["ChatThrottleLib"] = _G.ChatThrottleLib.SendChatMessage - function ChatThrottleLib:SendChatMessage(prio, prefix, text, ...) + function _G.ChatThrottleLib:SendChatMessage(prio, prefix, text, ...) Grichelde:DebugPrint("ChatThrottleLib:SendChatMessage : Hook called") local replacedText = Grichelde:CheckAndReplace(text) - return Grichelde.hooks["ChatThrottleLib"](ChatThrottleLib, prio, prefix, replacedText, ...) + return Grichelde.hooks["ChatThrottleLib"](_G.ChatThrottleLib, prio, prefix, replacedText, ...) end end end @@ -117,328 +108,4 @@ function Grichelde:HookIntoForOtherChatAddons(event, addonName) end end end -end - ---- Before af chat message is sent, check if replacement is required and replace the text accordingly. ---- @param message string ---- @param type string ---- @param language string ---- @param channel string -function Grichelde:SendChatMessage(message, type, language, channel, ...) - local replacedText = self:CheckAndReplace(message, type) - - self:DebugPrint("SendChatMessage : replacedText: " .. replacedText) - - -- Send text in chunks if length exceeds 255 bytes after replacement - local chunks = self:SplitText(replacedText) - self:DebugPrint("SendChatMessage : #chunks: " .. #chunks) - - for _, chunk in ipairs(chunks) do - self.hooks["SendChatMessage"](chunk, type, language, channel, ...); - end -end - -function Grichelde:CheckAndReplace(message, type) - local text = message - if (Misspelled) then - self:DebugPrint("Misspelled detected: cleansing message") - text = Misspelled:RemoveHighlighting(text) - end - text = strtrim(text) - - if (self:CheckReplacement(text, type)) then - text = self:ReplaceText(text) - end - return text -end - -function Grichelde:CheckReplacement(text, type) - if (not Grichelde.config.enabled) then - self:DebugPrint("CheckReplacement : globally disabled") - return false - end - - -- check type - if (not tContains(Grichelde.config.channels, type)) then - self:DebugPrint("CheckReplacement : skip channel type") - return false - end - - -- don't replace slash commands except chat related commands - if string_sub(text, 1, 1) == "/" then - local firstWord, _ = self:SplitOnFirstMatch(text) - if (firstWord == nil or not tContains(Grichelde.config.slashCommands, firstWord)) then - self:DebugPrint("CheckReplacement : ignore slash command") - return false - end - end - - -- in any other case - return true -end - ---- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures or raid target icons ---- and returns the end location of the match, or 0 if no pattern was found ---- @param text string ---- @return number -function Grichelde:CheckForPreversableText(text) - self:DebugPrint("CheckForPreversableText : text is " .. text) - - -- do not replace these patterns - local ignorePatterns = { - "|[Cc]%x%x%x%x%x%x%x%x.-|r", -- colored items (or links) - "|H.-|h", -- item links (http://www.wowwiki.com/ItemLink) - "|T.-|t", -- textures - "|n", -- newline - "{rt[1-8]}", -- rumbered raid target icons - "{Star}", -- named raid target icon 1 - "{Circle}", -- named raid target icon 2 - "{Coin}", -- named raid target icon 2 - "{Diamond}", -- named raid target icon 3 - "{Triangle}", -- named raid target icon 4 - "{Moon}", -- named raid target icon 5 - "{Square}", -- named raid target icon 6 - "{Cross}", -- named raid target icon 7 - "{X}", -- named raid target icon 7 - "{Skull}" -- named raid target icon 8 - } - - -- Calling find on ever pattern might be inefficient but its way less code. - for _, pattern in ipairs(ignorePatterns) do - local pos1, pos2 = string_find(text, pattern) - if pos1 == 1 and pos2 ~= nil then - self:DebugPrint("CheckForPreversableText : Found ignore pattern \"%s\" at (%d, %d)", pattern, pos1, pos2) - return pos2 - end - end - self:DebugPrint("CheckForPreversableText : no ignore pattern found") - return 0 -end - ---- Replaces all character occurrences for which replacements have been defined in the options, ---- while preserving any itemLinks or textures. (http://www.wowwiki.com/ItemLink) ---- @param text string ---- @return string -function Grichelde:ReplaceText(text) - local finalText = "" - local newText = text - - -- don't replace non-chat related slash commands - local firstWord, line = self:SplitOnFirstMatch(text) - if (firstWord ~= nil and tContains(Grichelde.config.slashCommands, firstWord)) then - self:DebugPrint("ReplaceText : Found slash command %s", firstWord ) - -- skip chat slash command - finalText = finalText .. firstWord .. ' ' - newText = line - end - - local current = 1 - local lastStart = 1 - - while current <= string_len(newText) do - local currentChar = string_sub(newText, current, current) - self:DebugPrint("current/char : %s,%s", current, currentChar) - - if currentChar ~= '|' and currentChar ~= '{' then - current = current + 1 - else - - -- lookahead-check for itemLinks, textures and raid target icons - local textAhead = string_sub(newText, current) - local posEnd = self:CheckForPreversableText(textAhead) - if posEnd > 0 then - self:DebugPrint("ReplaceText : Found an ignore pattern") - - local textBehind = string_sub(newText, lastStart, current - 1) - local replacement = self:ReplaceCharacters(textBehind) - local preservedText = string_sub(textAhead, 1, posEnd) - - finalText = finalText .. replacement .. preservedText - current = current + posEnd - lastStart = current - self:DebugPrint("ReplaceText : restarting at " .. lastStart) - else - -- no corresponding end was found to start pattern, continue loop with next char - current = current + 1 - end - end - end - - -- cleanup remaining text to the end - local remainingText = string_sub(newText, lastStart) - local replacement = self:ReplaceCharacters(remainingText) - finalText = finalText .. replacement - - self:DebugPrint("ReplaceText : replaced \"" .. text .. "\"") - self:DebugPrint("ReplaceText : with \"" .. finalText .. "\"") - return finalText -end - ---- Replaces all character occurrences for which replacements have been defined in the options ---- @param text string ---- @return string -function Grichelde:ReplaceCharacters(text) - -- todo: read from options - -- todo: case (in)sensitivity - -- todo: consolidate consecutive - -- todo: prevent infinite loops - is that even possible? - local replacement = text - replacement = string_gsub(replacement, "s", "ch") - replacement = string_gsub(replacement, "S", "Ch") - replacement = string_gsub(replacement, "t", "k") - replacement = string_gsub(replacement, "T", "K") - self:DebugPrint("ReplaceCharacters : replaced \"%s\" with \"%s\"", text, replacement) - return replacement -end - ---- Splits a long text in longest possible chunks of <= 255 length, split at last available space ---- @param text string ---- @return table -function Grichelde:SplitText(text) - local chunks = {} - local splitText = text - local textSize = string_len(splitText) - - while textSize > 255 do - local chunk = string_sub(splitText, 1, 255) - local remaining = "" - - -- special case: if space is the start of the next chunk, don't split this chunk - if string_sub(splitText, 256, 256) ~= ' ' then - -- split at last space, don't assign directly as nil might be returned - local left, right = self:SplitOnLastMatch(chunk) - if left ~= nil then - chunk = left - end - if right ~= nil then - remaining = right - end - end - - self:DebugPrint("SplitText : chunk: " .. chunk ) - - tInsert(chunks, chunk) - splitText = remaining .. string_sub(splitText, 256) - textSize = string_len(splitText) - end - - -- pickup remaining text < 255 - self:DebugPrint("SplitText : last chunk: " .. splitText) - tInsert(chunks, splitText) - - return chunks -end - --- split first word of a text line -function Grichelde:SplitOnFirstMatch(text, start) - self:DebugPrint("SplitOnFirstMatch : text: %s, start: %d", text, start) - local pos = start or 1 - local left, right = strmatch(text, "^.- .+", pos) - self:DebugPrint("SplitOnFirstMatch : left: %s, right: %s", left, right) - return left, right -end - -function Grichelde:SplitOnLastMatch(text, start) - self:DebugPrint("SplitOnLastMatch : text: %s, start: %d", text, start) - local pos = start or 1 - local left, right = strmatch(text, ".+ .-$", pos) - self:DebugPrint("SplitOnLastMatch : left: %s, right: %s", left, right) - return left, right -end - -function Grichelde:Format(message,...) - local msg = message - local l = select("#", ...) - if l > 0 then - -- sanitize nil values in vararg - local packed = {...} - for i = 1,l do - packed[i] = packed[i] or "nil" - end --- print("packed = ", packed) --- self:tPrint(packed) - -- cannot assign unpacked to a vararg variable and print it for debug - msg = string_fmt(message, unpack(packed)) - end - return msg or "nil" -end - -function Grichelde:Print(...) - print(self:Format(...)) -end - -local function prefixedPrint(colorCode, prefix, endClose, ...) - print(colorCode .. prefix .. endClose .. ": " .. ...) -end - -function Grichelde:PrefixedPrint(...) - prefixedPrint(PREFIX_COLOR_CODE, self.L.AddonName, _G.FONT_COLOR_CODE_CLOSE, self:Format(...)) -end - -function Grichelde:DebugPrint(...) - if (self.debug) then - prefixedPrint(_G.GRAY_FONT_COLOR_CODE, self.L.AddonName, _G.FONT_COLOR_CODE_CLOSE, self:Format(...)) - end -end - -local function tLen(t) - local count = 0 - for _ in pairs(t) do count = count + 1 end - return count -end - --- show strings differently to distinguish them from numbers -function Grichelde:PlainValue(val) - if val == nil then - return "" - elseif type(val) == "string" then - return '"' .. val .. '"' - elseif type(val) == "table" then - if tLen(val) > 0 then - return tostring(val) - else - return "{}" - end - else - return tostring(val) - end -end - ---- Prints any value to default channel, do NOT return a string. -function Grichelde:tPrint(val, indent, known) - if (not self.debug) then return end - - indent = indent or 0 - known = known or {} - - if val == nil then - print(string_rep(" ", indent) .. "") - elseif type(val) == "string" then - print(string_rep(" ", indent) .. "\"" .. val .. "\"") - elseif type(val) == "table" then - if tLen(val) > 0 then - for key, value in pairs(val) do - if value == nil then - print(string_rep(" ", indent) .. self:PlainValue(key) .. "= ") - elseif type(value) == "table" then - print(string_rep(" ", indent) .. self:PlainValue(key) .. "= {") - if tLen(value) > 0 then - if not known[value] then - self:tPrint(value, indent + 4, known) - known[value] = true - else - print(" " .. self:PlainValue(value)) - end - end - print(string_rep(" ", indent) .. "}") - else - print(string_rep(" ", indent) .. self:PlainValue(key) .. " = ".. self:PlainValue(value)) - end - end - else - print(string_rep(" ", indent) .. "{}") - end - else - print(string_rep(" ", indent) .. tostring(val)) - end -end +end \ No newline at end of file diff --git a/grichelde.tga b/Grichelde.tga similarity index 100% rename from grichelde.tga rename to Grichelde.tga diff --git a/Grichelde.toc b/Grichelde.toc index 5f28d26..48dea32 100644 --- a/Grichelde.toc +++ b/Grichelde.toc @@ -3,7 +3,7 @@ ## Title: Grichelde ## Notes: Replaces characters you type in the chat box ## Notes-de: Ersetzt eingegebene Zeichen in der Chat-Zeile -## Version: 0.3.0 +## Version: 0.4.0 ## Author: Teilzeit-Jedi ## eMail: tj@teilzeit-jedi.de @@ -20,4 +20,7 @@ libs.xml localisation.xml Grichelde.lua +GricheldeConstants.lua +GricheldeUtils.lua +GricheldeChat.lua GricheldeOptions.lua diff --git a/GricheldeChat.lua b/GricheldeChat.lua new file mode 100644 index 0000000..29dcbd5 --- /dev/null +++ b/GricheldeChat.lua @@ -0,0 +1,245 @@ +-- import addon read namespace from global env +local _G = _G +local Grichelde = _G.Grichelde + +local ipairs, tContains, tFilter, tInsert, tConcat, find, sub, gsub, match, toLower, trim, length + = Grichelde.functions.ipairs, Grichelde.functions.tContains, Grichelde.functions.tFilter, Grichelde.functions.tInsert, Grichelde.functions.tConcat, Grichelde.functions.find, Grichelde.functions.sub, Grichelde.functions.gsub, Grichelde.functions.match, Grichelde.functions.toLower, Grichelde.functions.trim, Grichelde.functions.length + +--- Before a chat message is sent, check if replacement is required and replace the text accordingly. +--- @param message string +--- @param type string +--- @param language string +--- @param channel string +function Grichelde:SendChatMessage(message, type, language, channel, ...) + local replacedText = self:CheckAndReplace(message, type) + + self:DebugPrint("SendChatMessage : replacedText: " .. replacedText) + + -- Send text in chunks if length exceeds 255 bytes after replacement + local chunks = self:SplitText(replacedText) + self:DebugPrint("SendChatMessage : #chunks: " .. #chunks) + + for _, chunk in ipairs(chunks) do + self.hooks["SendChatMessage"](chunk, type, language, channel, ...); + end +end + +function Grichelde:CheckAndReplace(message, type) + local text = message + if (self:CheckReplacement(text, type)) then + if (_G.Misspelled) then + self:DebugPrint("Misspelled detected: cleansing message") + text = _G.Misspelled:RemoveHighlighting(text) + end + text = self:ReplaceText(trim(text)) + end + return text +end + +function Grichelde:CheckReplacement(text, channel) + if (not self.db.profile.enabled) then + self:DebugPrint("CheckReplacement : disabled") + return false + end + + -- check channel type + local allowedChannels = tFilter(self.db.profile.channels, + function(_,v) return v == true end, + function(k,_) return k end + ) + self:DebugPrint("CheckReplacement : allowed channels: %s", tConcat(allowedChannels, ", ")) + local type = self:ConvertBlizChannelToType(channel) + if (type ~= nil and not tContains(allowedChannels, type)) then + self:DebugPrint("CheckReplacement : skip channel type %s", type) + return false + end + + -- don't replace slash commands except chat related commands + if sub(text, 1, 1) == "/" then + local firstWord, _ = self:SplitOnFirstMatch(text) + -- todo: adapt allowed slash commands + if (firstWord == nil or not tContains(self.slashCommands, firstWord)) then + self:DebugPrint("CheckReplacement : ignore slash command") + return false + end + end + + -- in any other case + return true +end + +function Grichelde:ConvertBlizChannelToType(channel) + local type = toLower(channel) + self:DebugPrint("ConvertBlizChannelToType : convert %s to %s", channel, type) + return type +end + +--- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures or raid target icons +--- and returns the end location of the match, or 0 if no pattern was found +--- @param text string +--- @return number +function Grichelde:CheckForPreversableText(text) + self:DebugPrint("CheckForPreversableText : text is " .. text) + + -- do not replace these patterns + local ignorePatterns = { + "|[Cc]%x%x%x%x%x%x%x%x.-|r", -- colored items (or links) + "|H.-|h", -- item links (http://www.wowwiki.com/ItemLink) + "|T.-|t", -- textures + "|n", -- newline + "{rt[1-8]}", -- rumbered raid target icons + "{Star}", -- named raid target icon 1 + "{Circle}", -- named raid target icon 2 + "{Coin}", -- named raid target icon 2 + "{Diamond}", -- named raid target icon 3 + "{Triangle}", -- named raid target icon 4 + "{Moon}", -- named raid target icon 5 + "{Square}", -- named raid target icon 6 + "{Cross}", -- named raid target icon 7 + "{X}", -- named raid target icon 7 + "{Skull}" -- named raid target icon 8 + } + + -- Calling find on ever pattern might be inefficient but its way less code. + for _, pattern in ipairs(ignorePatterns) do + local pos1, pos2 = find(text, pattern) + if pos1 == 1 and pos2 ~= nil then + self:DebugPrint("CheckForPreversableText : Found ignore pattern \"%s\" at (%d, %d)", pattern, pos1, pos2) + return pos2 + end + end + self:DebugPrint("CheckForPreversableText : no ignore pattern found") + return 0 +end + +--- Replaces all character occurrences for which replacements have been defined in the options, +--- while preserving any itemLinks or textures. (http://www.wowwiki.com/ItemLink) +--- @param text string +--- @return string +function Grichelde:ReplaceText(text) + local finalText = "" + local newText = text + + -- don't replace non-chat related slash commands + local firstWord, line = self:SplitOnFirstMatch(text) + if (firstWord ~= nil and tContains(self.slashCommands, firstWord)) then + self:DebugPrint("ReplaceText : Found slash command %s", firstWord ) + -- skip chat slash command + finalText = finalText .. firstWord .. ' ' + newText = line + end + + local current = 1 + local lastStart = 1 + + while current <= length(newText) do + local currentChar = sub(newText, current, current) + self:DebugPrint("current/char : %s,%s", current, currentChar) + + if currentChar ~= '|' and currentChar ~= '{' then + current = current + 1 + else + + -- lookahead-check for itemLinks, textures and raid target icons + local textAhead = sub(newText, current) + local posEnd = self:CheckForPreversableText(textAhead) + if posEnd > 0 then + self:DebugPrint("ReplaceText : Found an ignore pattern") + + local textBehind = sub(newText, lastStart, current - 1) + local replacement = self:ReplaceCharacters(textBehind) + local preservedText = sub(textAhead, 1, posEnd) + + finalText = finalText .. replacement .. preservedText + current = current + posEnd + lastStart = current + self:DebugPrint("ReplaceText : restarting at " .. lastStart) + else + -- no corresponding end was found to start pattern, continue loop with next char + current = current + 1 + end + end + end + + -- cleanup remaining text to the end + local remainingText = sub(newText, lastStart) + local replacement = self:ReplaceCharacters(remainingText) + finalText = finalText .. replacement + + self:DebugPrint("ReplaceText : replaced \"" .. text .. "\"") + self:DebugPrint("ReplaceText : with \"" .. finalText .. "\"") + return finalText +end + +--- Replaces all character occurrences for which replacements have been defined in the options +--- @param text string +--- @return string +function Grichelde:ReplaceCharacters(text) + -- todo: read from options + -- todo: case (in)sensitivity + -- todo: consolidate consecutive + -- todo: prevent infinite loops - is that even possible? + local replacement = text + replacement = gsub(replacement, "s", "ch") + replacement = gsub(replacement, "S", "Ch") + replacement = gsub(replacement, "t", "k") + replacement = gsub(replacement, "T", "K") + self:DebugPrint("ReplaceCharacters : replaced \"%s\" with \"%s\"", text, replacement) + return replacement +end + +--- Splits a long text in longest possible chunks of <= 255 length, split at last available space +--- @param text string +--- @return table +function Grichelde:SplitText(text) + local chunks = {} + local splitText = text + local textSize = length(splitText) + + while textSize > 255 do + local chunk = sub(splitText, 1, 255) + local remaining = "" + + -- special case: if space is the start of the next chunk, don't split this chunk + if sub(splitText, 256, 256) ~= ' ' then + -- split at last space, don't assign directly as nil might be returned + local left, right = self:SplitOnLastMatch(chunk) + if left ~= nil then + chunk = left + end + if right ~= nil then + remaining = right + end + end + + self:DebugPrint("SplitText : chunk: " .. chunk ) + + tInsert(chunks, chunk) + splitText = remaining .. sub(splitText, 256) + textSize = length(splitText) + end + + -- pickup remaining text < 255 + self:DebugPrint("SplitText : last chunk: " .. splitText) + tInsert(chunks, splitText) + + return chunks +end + +-- split at first word of a text line +function Grichelde:SplitOnFirstMatch(text, start) + self:DebugPrint("SplitOnFirstMatch : text: %s, start: %d", text, start) + local pos = start or 1 + local left, right = match(text, "^.- .+", pos) + self:DebugPrint("SplitOnFirstMatch : left: %s, right: %s", left, right) + return left or text, right +end + +-- split at last word of a text line +function Grichelde:SplitOnLastMatch(text, start) + self:DebugPrint("SplitOnLastMatch : text: %s, start: %d", text, start) + local pos = start or 1 + local left, right = match(text, ".+ .-$", pos) + self:DebugPrint("SplitOnLastMatch : left: %s, right: %s", left, right) + return left, right or text +end diff --git a/GricheldeConstants.lua b/GricheldeConstants.lua new file mode 100644 index 0000000..bd585db --- /dev/null +++ b/GricheldeConstants.lua @@ -0,0 +1,94 @@ +-- read namespace from global env +local _G = _G +local Grichelde = _G.Grichelde + +-- upvalues and constants + +-- faster function lookups by mapping to local refs +Grichelde.functions = {} +Grichelde.functions.type = _G.type +Grichelde.functions.print = _G.print +Grichelde.functions.pairs = _G.pairs +Grichelde.functions.ipairs = _G.ipairs +Grichelde.functions.tContains = _G.tContains +Grichelde.functions.tFilter = function(t, cond, extr) + local filtered = {} + for key, value in Grichelde.functions.pairs(t) do + if cond(key, value) then + local val = extr(key, value) + Grichelde.functions.tInsert(filtered, #filtered + 1, val) + end + end + return filtered +end +Grichelde.functions.tInsert = _G.table.insert +Grichelde.functions.tConcat = _G.table.concat +Grichelde.functions.select = _G.select +Grichelde.functions.unpack = _G.unpack +Grichelde.functions.find = _G.string.find +Grichelde.functions.sub = _G.string.sub +Grichelde.functions.gsub = _G.string.gsub +Grichelde.functions.match = _G.strmatch +Grichelde.functions.join = _G.strjoin +Grichelde.functions.toLower = _G.strlower +Grichelde.functions.toUpper = _G.strupper +Grichelde.functions.format = _G.string.format +Grichelde.functions.rep = _G.string.rep +Grichelde.functions.trim = _G.strtrim +Grichelde.functions.length = _G.string.len +Grichelde.functions.toString = _G.tostring + +-- colors: +Grichelde.COLOR_CODES = {} +Grichelde.COLOR_CODES.PREFIX = "|c00FFAA00" +-- https://github.com/stoneharry/Misc-WoW-Stuff/blob/master/EoC%20Interface/FrameXML/Constants.lua +Grichelde.COLOR_CODES.NORMAL = _G.NORMAL_FONT_COLOR_CODE or "|cffffd200"; +Grichelde.COLOR_CODES.HIGHLIGHT = _G.HIGHLIGHT_FONT_COLOR_CODE or "|cffffffff"; +Grichelde.COLOR_CODES.RED = _G.RED_FONT_COLOR_CODE or "|cffff2020"; +Grichelde.COLOR_CODES.GREEN = _G.GREEN_FONT_COLOR_CODE or "|cff20ff20"; +Grichelde.COLOR_CODES.GRAY = _G.GRAY_FONT_COLOR_CODE or "|cff808080"; +Grichelde.COLOR_CODES.YELLOW = _G.YELLOW_FONT_COLOR_CODE or "|cffffff00"; +Grichelde.COLOR_CODES.LIGHTYELLOW = _G.LIGHTYELLOW_FONT_COLOR_CODE or "|cffffff9a"; +Grichelde.COLOR_CODES.ORANGE = _G.ORANGE_FONT_COLOR_CODE or "|cffff7f3f"; +Grichelde.COLOR_CODES.CLOSE = _G.FONT_COLOR_CODE_CLOSE or "|r"; + +Grichelde.slashCommands = { "/s", "/say", "/e", "/em", "/me", "/emote", "/y", "/yell", "/sh", "/shout", "/p", "/party", "/pl", "/partyleader", "/g", "/gc", "/guild", "/o", "/osay", "/officer", "/raid", "/rsay", "/rl", "/raidleader", "/rw", "/raidwarning", "/i", "/instance", "/bg", "/battleground", "/w", "/whisper", "/t", "/tell", "/send", "/r", "/reply" } + +Grichelde.defaultConfig = { + global = {}, + profile = { + enabled = true, + channels = { + ["*"] = false, + say = true, + emote = false, + yell = true, + party = true, + partyLeader = true, + guild = true, + officer = true, + }, + replacements = { + ["**"] = { + searchText = "", + replaceText = "", + caseSensitive = false, + consolidate = true, + }, + replacement_0 = { + order = 1, + searchText = "s", + replaceText = "ch", + caseSensitive = false, + consolidate = true, + }, + replacement_1 = { + order = 2, + searchText = "t", + replaceText = "ck", + caseSensitive = false, + consolidate = true, + } + } + } +} \ No newline at end of file diff --git a/GricheldeOptions.lua b/GricheldeOptions.lua index a5136d4..08c02e9 100644 --- a/GricheldeOptions.lua +++ b/GricheldeOptions.lua @@ -1,4 +1,8 @@ -local AddonName, _ = ... +-- read namespace from global env +local _G = _G +local Grichelde = _G.Grichelde + +local unpack, join, toString = Grichelde.functions.unpack, Grichelde.functions.join, Grichelde.functions.toString function Grichelde:CreateOptionsUI() return { @@ -15,8 +19,6 @@ function Grichelde:CreateOptionsUI() type = "toggle", name = self.L.Options_Enabled_Name, desc = self:Format(self.L.Options_Enabled_Desc, self.L.AddonName), - set = function(info, val) self.db.profile.enabled=val end, - get = function(info) return self.db.profile.enabled end, disabled = false, }, @@ -80,20 +82,26 @@ function Grichelde:CreateOptionsUI() name = self.L.Options_Channels_ChannelRaidLeader_Name, desc = self:Format(self.L.Options_Channels_ChannelRaidLeader_Desc, self.L.AddonName), }, - instance = { + raidWarning = { order = 9, type = "toggle", + name = self.L.Options_Channels_ChannelRaidWarning_Name, + desc = self:Format(self.L.Options_Channels_ChannelRaidWarning_Desc, self.L.AddonName), + }, + instance = { + order = 10, + type = "toggle", name = self.L.Options_Channels_ChannelInstance_Name, desc = self:Format(self.L.Options_Channels_ChannelInstance_Desc, self.L.AddonName), }, battleground = { - order = 10, + order = 11, type = "toggle", name = self.L.Options_Channels_ChannelBattleground_Name, desc = self:Format(self.L.Options_Channels_ChannelBattleground_Desc, self.L.AddonName), }, whisper = { - order = 11, + order = 12, type = "toggle", name = self.L.Options_Channels_ChannelWhisper_Name, desc = self:Format(self.L.Options_Channels_ChannelWhisper_Desc, self.L.AddonName), @@ -167,51 +175,20 @@ function Grichelde:CreateOptionsUI() } end -local Grichelde_DefaultConfig = { - global = {}, - profile = { - enabled = true, - channels = { - ["*"] = false, - say = true, - emote = false, - yell = true, - party = true, - partyLeader = true, - guild = true, - officer = true, - }, - replacements = { - ["**"] = { - searchText = "", - replaceText = "", - caseSensitive = false, - consolidate = true, - }, - replacement_0 = { - order = 1, - searchText = "s", - replaceText = "ch", - caseSensitive = false, - consolidate = true, - }, - replacement_1 = { - order = 2, - searchText = "t", - replaceText = "ck", - caseSensitive = false, - consolidate = true, - } - } - } -} - function Grichelde:LoadDatabase() -- Called when the addon is loaded - self.db = LibStub("AceDB-3.0"):New(AddonName .."DB", Grichelde_DefaultConfig, true) - self.db.RegisterCallback(self, "OnProfileChanged", "RefreshConfig") - self.db.RegisterCallback(self, "OnProfileCopied", "RefreshConfig") - self.db.RegisterCallback(self, "OnProfileReset", "RefreshConfig") + self.db = LibStub("AceDB-3.0"):New(self.name .."DB", self.defaultConfig, true) + + -- todo: is this really needed? + self.db.RegisterCallback(self, "OnProfileChanged", function (event) + self:PrefixedPrint(self.L.Profiles_Loaded, self.COLOR_CODES.GREEN .. self.db:GetCurrentProfile() .. self.COLOR_CODES.CLOSE) + end) + self.db.RegisterCallback(self, "OnProfileCopied", function(event) + self:PrefixedPrint(self.L.Profiles_Copied, self.COLOR_CODES.GREEN .. self.db:GetCurrentProfile() .. self.COLOR_CODES.CLOSE) + end) + self.db.RegisterCallback(self, "OnProfileReset", function(event) + self:PrefixedPrint(self.L.Profiles_Reset, self.COLOR_CODES.GREEN .. self.db:GetCurrentProfile() .. self.COLOR_CODES.CLOSE) + end) end function Grichelde:SetupOptions() @@ -221,27 +198,12 @@ function Grichelde:SetupOptions() self.options.args.profiles.disabled = false local activeProfile = self.db:GetCurrentProfile() - self:PrefixedPrint(self.L.Profiles_Loaded, _G.GREEN_FONT_COLOR_CODE, activeProfile, "|r") + self:PrefixedPrint(self.L.Profiles_Loaded, self.COLOR_CODES.GREEN .. activeProfile .. self.COLOR_CODES.CLOSE) self:tPrint(self.db.profile) -- Adding options to blizzard frame - LibStub("AceConfig-3.0"):RegisterOptionsTable(AddonName, self.options) - LibStub("AceConfigDialog-3.0"):AddToBlizOptions(AddonName, self.L.AddonName) -end - -function Grichelde:SetupSlashCommands() - local function HandleSlashCommand(input) - -- Show the GUI if no input is supplied, otherwise handle the chat input. - if not input or input:trim() == "" then - LibStub("AceConfigDialog-3.0"):Open(AddonName) - else - -- handle slash ourselves - self:Print("Handle slash command: " .. input) - end - end - - self:RegisterChatCommand("grichelde", HandleSlashCommand) - self:RegisterChatCommand("gri", HandleSlashCommand) + LibStub("AceConfig-3.0"):RegisterOptionsTable(self.name, self.options) + LibStub("AceConfigDialog-3.0"):AddToBlizOptions(self.name, self.L.AddonName) end function Grichelde:SyncToDatabase(info, val) @@ -251,8 +213,8 @@ function Grichelde:SyncToDatabase(info, val) option = option[info[path]] -- or nil path = path + 1 end - local optionPath = strjoin(".", unpack(info, 1, #info)) - self:DebugPrint("change option \"%s\" from %s to %s", optionPath, tostring(option[info[path]]), tostring(val)) + local optionPath = join(".", unpack(info, 1, #info)) + self:DebugPrint("change option \"%s\" from %s to %s", optionPath, toString(option[info[path]]), toString(val)) option[info[path]] = val end @@ -263,8 +225,8 @@ function Grichelde:ReadFromDatabase(info) option = option[info[path]] -- or nil path = path + 1 end - local optionPath = strjoin(".", unpack(info, 1, #info)) - self:DebugPrint("read option \"%s\": %s", optionPath, tostring(option)) + local optionPath = join(".", unpack(info, 1, #info)) + self:DebugPrint("read option \"%s\": %s", optionPath, toString(option)) return option end @@ -273,8 +235,4 @@ function Grichelde:IsDisabled(info) return false end return not self.db.profile.enabled -end - -function Grichelde:RefreshConfig(event) - self:Print(self.L.Profiles_Refreshed, _G.GREEN_FONT_COLOR_CODE, self.db:GetCurrentProfile(), "|r") -end +end \ No newline at end of file diff --git a/GricheldeUtils.lua b/GricheldeUtils.lua new file mode 100644 index 0000000..3a4445b --- /dev/null +++ b/GricheldeUtils.lua @@ -0,0 +1,103 @@ +-- import addon read namespace from global env +local _G = _G +local Grichelde = _G.Grichelde + +local type, print, pairs, select, unpack, format, rep, toString += Grichelde.functions.type, Grichelde.functions.print, Grichelde.functions.pairs, Grichelde.functions.select, Grichelde.functions.unpack, Grichelde.functions.format, Grichelde.functions.rep, Grichelde.functions.toString + +function Grichelde:Format(message, ...) + local msg = message + local l = select("#", ...) + if l > 0 then + -- sanitize nil values in vararg + local packed = { ... } + for i = 1, l do + packed[i] = toString(packed[i]) or "nil" + end + -- print("packed = ", packed) + -- self:tPrint(packed) + -- cannot assign unpacked to a vararg variable and print it for debug + msg = format(message, unpack(packed)) + end + return msg or "nil" +end + +function Grichelde:Print(...) + print(self:Format(...)) +end + +local function prefixedPrint(colorCode, prefix, endClose, ...) + print(colorCode .. prefix .. endClose .. ": " .. ...) +end + +function Grichelde:PrefixedPrint(...) + prefixedPrint(self.COLOR_CODES.PREFIX, self.L.AddonName, self.COLOR_CODES.CLOSE, self:Format(...)) +end + +function Grichelde:DebugPrint(...) + if (self.debug) then + prefixedPrint(self.COLOR_CODES.GRAY, self.L.AddonName, self.COLOR_CODES.CLOSE, self:Format(...)) + end +end + +local function tLen(t) + local count = 0 + for _ in pairs(t) do count = count + 1 end + return count +end + +-- show strings differently to distinguish them from numbers +function Grichelde:PlainValue(val) + if val == nil then + return "" + elseif type(val) == "string" then + return '"' .. val .. '"' + elseif type(val) == "table" then + if tLen(val) > 0 then + return toString(val) + else + return "{}" + end + else + return toString(val) + end +end + +--- Prints any value to default channel, do NOT return a string. +function Grichelde:tPrint(val, indent, known) + if (not self.debug) then return end + + indent = indent or 0 + known = known or {} + + if val == nil then + print(rep(" ", indent) .. "") + elseif type(val) == "string" then + print(rep(" ", indent) .. "\"" .. val .. "\"") + elseif type(val) == "table" then + if tLen(val) > 0 then + for key, value in pairs(val) do + if value == nil then + print(rep(" ", indent) .. self:PlainValue(key) .. "= ") + elseif type(value) == "table" then + print(rep(" ", indent) .. self:PlainValue(key) .. "= {") + if tLen(value) > 0 then + if not known[value] then + self:tPrint(value, indent + 4, known) + known[value] = true + else + print(" " .. self:PlainValue(value)) + end + end + print(rep(" ", indent) .. "}") + else + print(rep(" ", indent) .. self:PlainValue(key) .. " = " .. self:PlainValue(value)) + end + end + else + print(rep(" ", indent) .. "{}") + end + else + print(rep(" ", indent) .. toString(val)) + end +end diff --git a/localisation/deDE.lua b/localisation/deDE.lua index 4c6c6fc..a40a31c 100644 --- a/localisation/deDE.lua +++ b/localisation/deDE.lua @@ -9,13 +9,13 @@ L.Addon_Detected_WIM = "Das Addon 'WIM' wurde erkannt und alle Flüsternnachrich -- profiles L.Profiles_Available = "Verf\195\188gbare Profile:" -L.Profiles_Created = "Neues Profil %s%s%s angelegt." -L.Profiles_Loaded = "Profil %s%s%s geladen." -L.Profiles_Refreshed = "Profil %s%s%s aktualisiert." -L.Profiles_Deleted = "Profil %s%s%s gel\195\182scht." -L.Profiles_Copied = "Einstellungen von Profil %s%s%s \195\188bernommen." -L.Profiles_Reset = "Profil %s%s%s zur\195\188ckgesetzt." -L.Profiles_Invalid = "Ung\195\188ltiges Profil %s%s%s!" +L.Profiles_Created = "Neues Profil %s angelegt." +L.Profiles_Loaded = "Profil %s geladen." +L.Profiles_Refreshed = "Profil %s aktualisiert." +L.Profiles_Deleted = "Profil %s gel\195\182scht." +L.Profiles_Copied = "Einstellungen von Profil %s \195\188bernommen." +L.Profiles_Reset = "Profil %s zur\195\188ckgesetzt." +L.Profiles_Invalid = "Ung\195\188ltiges Profil %s!" L.Profiles_DeleteError = "Das aktive Profil kann nicht gel\195\182scht werden!" -- options @@ -43,6 +43,8 @@ L.Options_Channels_ChannelRaid_Name = "Schlachtzug" L.Options_Channels_ChannelRaid_Desc = "Aktiviert %s im Kanal \"Schlachtzug\"." L.Options_Channels_ChannelRaidLeader_Name = "Schlachtzugsanf\195\188hrer" L.Options_Channels_ChannelRaidLeader_Desc = "Aktiviert %s im Kanal \"Schlachtzugsanf\195\188hrer\"." +L.Options_Channels_ChannelRaidWarning_Name = "Schlachtzugswarnung" +L.Options_Channels_ChannelRaidWarning_Desc = "Aktiviert %s im Kanal \"Schlachtzugswarnung." L.Options_Channels_ChannelInstance_Name = "Instanz" L.Options_Channels_ChannelInstance_Desc = "Aktiviert %s im Kanal \"Instanz\"." L.Options_Channels_ChannelBattleground_Name = "Schlachtfeld" diff --git a/localisation/enUS.lua b/localisation/enUS.lua index 580ed3f..4cca988 100644 --- a/localisation/enUS.lua +++ b/localisation/enUS.lua @@ -10,13 +10,13 @@ L.Addon_Detected_WIM = "Das Addon 'WIM' has been detected and any whispers will -- profiles L.Profiles_Available = "Available profiles:" -L.Profiles_Created = "New profile \"%s\" created." -L.Profiles_Loaded = "Profile %s%s%s is loaded." -L.Profiles_Refreshed = "Profil %s%s%s refreshed." -L.Profiles_Deleted = "Profile %s%s%s deleted." -L.Profiles_Copied = "Settings applied from profile %s%s%s." -L.Profiles_Reset = "Profil %s%s%s reset." -L.Profiles_Invalid = "Invalid profile %s%s%s!" +L.Profiles_Created = "New profile %s created." +L.Profiles_Loaded = "Profile %s is loaded." +L.Profiles_Refreshed = "Profil %s refreshed." +L.Profiles_Deleted = "Profile %s deleted." +L.Profiles_Copied = "Settings applied from profile %s." +L.Profiles_Reset = "Profil %s reset." +L.Profiles_Invalid = "Invalid profile %s!" L.Profiles_DeleteError = "The active profile cannot be deleted!" -- options @@ -44,6 +44,8 @@ L.Options_Channels_ChannelRaid_Name = "Raid" L.Options_Channels_ChannelRaid_Desc = "Activates %s in channel \"Raid\"." L.Options_Channels_ChannelRaidLeader_Name = "Raid Leader" L.Options_Channels_ChannelRaidLeader_Desc = "Activates %s in channel \"Raid Leader\"." +L.Options_Channels_ChannelRaidWarning_Name = "Raid Warning" +L.Options_Channels_ChannelRaidWarning_Desc = "Activates %s in channel \"Raid Warning\"." L.Options_Channels_ChannelInstance_Name = "Instance" L.Options_Channels_ChannelInstance_Desc = "Activates %s in channel \"Instance\"." L.Options_Channels_ChannelBattleground_Name = "Battleground"