diff --git a/CHANGELOG.md b/CHANGELOG.md index 24a20b8..fa6384c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Version 0.9.0-rc - 2020-07-25 [Release Candidate] +### Added +- enable/disable from slash command +- matching conditions (never, always, start, end, start or end) +- support capturing groups +- import examples +- testing capabilities +- compatibility with WoW Retail +### Changed +- adapted help texts +- spelling errors + ## Version 0.8.1-beta - 2020-06-16 ### Added - stop replacements per mapping diff --git a/Grichelde.lua b/Grichelde.lua index 9d7b019..c8a51ae 100644 --- a/Grichelde.lua +++ b/Grichelde.lua @@ -48,9 +48,9 @@ function Grichelde:OnEnable() self:SetupSlashCommands() -- tell the world we are listening - if self.db.profile.enabled then + if (self.db.profile.enabled == true) then local namePlusVersion = self:Format(self.L.AddonNamePlusVersion, self.L.AddonName, self.version) - self:Print(self.L.AddonLoaded, self.COLOR_CODES.PREFIX .. namePlusVersion .. self.COLOR_CODES.CLOSE) + self.F.print(self:Format(self.L.AddonLoaded, self.COLOR_CODES.PREFIX .. namePlusVersion .. self.COLOR_CODES.CLOSE)) end end @@ -70,7 +70,7 @@ end function Grichelde:HandleSlashCommand(input, ...) -- Show the GUI if no input is supplied, otherwise handle the chat input. - if self.functions.nilOrEmpty(input) then + if (self.F.nilOrEmpty(input)) then self:ToggleOptions() else -- handle slash ourselves @@ -81,6 +81,10 @@ function Grichelde:HandleSlashCommand(input, ...) self:ToggleOptions() elseif input == "profile" then self:PrintProfile() + elseif input == "on" or input == "enable" then + self:Activate() + elseif input == "off" or input == "disable" then + self:Deactivate() else self:SendChatMessageOverride(input, ...) end diff --git a/Grichelde.toc b/Grichelde.toc index 07f492d..5495054 100644 --- a/Grichelde.toc +++ b/Grichelde.toc @@ -1,9 +1,9 @@ -## Interface: 11304 +## Interface: 11305 ## Title: Grichelde ## Notes: Replaces characters from the chat box ## Notes-de: Ersetzt eingegebene Zeichen in der Chat-Zeile -## Version: 0.8.1-beta +## Version: 0.9.0-rc ## Author: Teilzeit-Jedi ## eMail: tj@teilzeit-jedi.de @@ -30,3 +30,5 @@ GricheldeUpgrade.lua GricheldeOptions.lua GricheldeMinimap.lua GricheldeChat.lua + +GricheldeTest.lua \ No newline at end of file diff --git a/GricheldeChat.lua b/GricheldeChat.lua index 9143aae..e06cbe2 100644 --- a/GricheldeChat.lua +++ b/GricheldeChat.lua @@ -1,63 +1,54 @@ -- import addon read namespace from global env local _G = _G -local Grichelde = _G.Grichelde +local Grichelde = _G.Grichelde or {} -local IsAddOnLoaded, assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tFilter, tInsert, tConcat, tSize, tIsEmpty, find, sub, isUpper, isLower, toUpper, toLower, trim, length, lenUtf8 - = Grichelde.functions.IsAddOnLoaded, Grichelde.functions.assert, Grichelde.functions.nilOrEmpty, Grichelde.functions.pairs, Grichelde.functions.ipairs, Grichelde.functions.spairs, Grichelde.functions.tContains, Grichelde.functions.tFilter, Grichelde.functions.tInsert, Grichelde.functions.tConcat, Grichelde.functions.tSize, Grichelde.functions.tIsEmpty, - Grichelde.functions.find, Grichelde.functions.sub, Grichelde.functions.isUpper, Grichelde.functions.isLower, Grichelde.functions.toUpper, Grichelde.functions.toLower, Grichelde.functions.trim, Grichelde.functions.length, Grichelde.functions.lenUtf8 +local IsAddOnLoaded, assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tFilter, tInsert, tConcat, tSize, tIsEmpty, find, sub, gsub, gmatch, getNextCharUtf8, isUpper, isLower, toUpper, toLower, capitalize, trim, length, lengthUtf8 + = Grichelde.F.IsAddOnLoaded, Grichelde.F.assert, Grichelde.F.nilOrEmpty, Grichelde.F.pairs, Grichelde.F.ipairs, Grichelde.F.spairs, Grichelde.F.tContains, Grichelde.F.tFilter, Grichelde.F.tInsert, Grichelde.F.tConcat, Grichelde.F.tSize, Grichelde.F.tIsEmpty, + Grichelde.F.find, Grichelde.F.sub, Grichelde.F.gsub, Grichelde.F.gmatch, Grichelde.F.getNextCharUtf8, Grichelde.F.isUpper, Grichelde.F.isLower, Grichelde.F.toUpper, Grichelde.F.toLower, Grichelde.F.capitalize, Grichelde.F.trim, Grichelde.F.length, Grichelde.F.lengthUtf8 -local function cleanseMessage(this, message) - local text = message or "" - if (IsAddOnLoaded("Misspelled")) then - this:DebugPrint("Misspelled detected: cleansing message") - text = _G.Misspelled:RemoveHighlighting(message) - end - return trim(text) -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 or "") ---- 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, ...) - local text = cleanseMessage(self, message) - if (self:CheckReplacementAllowed(text, type)) then - text = self:ReplaceText(text) - end + while (textSize > 255) do + local chunk = sub(splitText, 1, 255) + local remaining = "" - self:SendChunkifiedChatMessage(text, type, ...) -end + -- 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 ---- Always replaces the text accoording to the configuration, even if activation or channel was disabled. ---- This is used the the override slash command: "/gri /emote text to replace". ---- NOTE: type and channel (in case of whispers) are determined from the message text. --- @param message string --- @param type string -function Grichelde:SendChatMessageOverride(message, ...) - local text = cleanseMessage(self, message) + self:DebugPrint("SplitText : chunk:", chunk) - local chatType, lang, channel = text, DEFAULT_CHAT_FRAME.editBox.chatType or "SAY", DEFAULT_CHAT_FRAME.editBox.languageID - local msg, type, chan = self:CheckAndExtractMessageTypeTarget(text) + tInsert(chunks, chunk) + splitText = remaining .. sub(splitText, 256) + textSize = length(splitText) + end - if msg ~= nil then - msg = self:ReplaceText(msg) + -- pickup remaining text < 255 + self:DebugPrint("SplitText : last chunk:", splitText) + tInsert(chunks, splitText) - if type ~= nil then - self:SendChunkifiedChatMessage(msg, type, lang, chan, ...) - else - self:SendChunkifiedChatMessage(msg, chatType, lang, channel, ...) - end - else - -- suppress invalid messages/channels/targets - end + return chunks end --- Send text in chunks if length exceeds 255 bytes after replacement. function Grichelde:SendChunkifiedChatMessage(message, ...) - if length(message) > 255 then + if (length(message) > 255) then local chunks = self:SplitText(message) - self:DebugPrint("SendChatMessage : #chunks:", #chunks) + self:DebugPrint("SendChunkifiedChatMessage : #chunks:", #chunks) for _, chunk in ipairs(chunks) do self.hooks["SendChatMessage"](chunk, ...); @@ -67,32 +58,556 @@ function Grichelde:SendChunkifiedChatMessage(message, ...) end end -local function IsOneBigEmote(this, text) - local firstWord, _ = this:SplitOnFirstMatch(text) +function Grichelde:ReplaceCharacters(text, replName, replTable, consolidate, replacedTexts) + local function convertToCaseInsensitivePattern(pattern) + local ciPattern = "" + local ignored = {'^', '$', '(', ')', '.'} + local quantifiers = {'*', '+', '-', '?'} + local pos = 1 + local p, patRest = getNextCharUtf8(pattern) + + while (p ~= nil) do + if (tContains(ignored, p) or tContains(quantifiers, p)) then + -- ignore + ciPattern = ciPattern .. p + elseif (p == "%") then + -- ignore capture references + p, patRest = getNextCharUtf8(patRest) + if (p ~= nil) then + ciPattern = ciPattern .. "%" .. p + end + elseif (p == "[") then + -- skip pattern sets + ciPattern = ciPattern .. "[" + p, patRest = getNextCharUtf8(patRest) + while ((p ~= nil) and (p ~= "]")) do + ciPattern = ciPattern .. Grichelde.F.toUpper(p) .. Grichelde.F.toLower(p) + p, patRest = getNextCharUtf8(patRest) + end + ciPattern = ciPattern .. "]" + else + ciPattern = ciPattern .. "[" .. Grichelde.F.toUpper(p) .. Grichelde.F.toLower(p) .. "]" + end + + p, patRest = getNextCharUtf8(patRest) + end + self:TracePrint("convertToCaseInsensitivePattern : %s => %s", pattern, ciPattern) + return ciPattern + end + + local function replaceCaptures(text, replaceText, captures) + local replText = replaceText + self:TracePrint("replaceCaptures : text: %s, #captures: %d", text, #captures) + + if (#captures > 0) then + for i, cap in ipairs(captures) do + --self:TracePrint("replaceCaptures : i: %d, cap: %s", i, cap) + if (cap == nil) then + break + else + local oldRepl = replText + replText = gsub(oldRepl, "%%" .. i, cap) + self:TracePrint("ReplaceCaptures : substitute capture %s: %s => %s", oldRepl, cap, replText) + end + end + else + self:TracePrint("ReplaceCaptures : no captures") + end + return replText + end + + --- this is more complicated to get it right than it looks like + local function applyCase(replRest, lastCase, nextCase) + local repl = "" + if (lastCase == nil) then + -- lastCase was unknown, always take over nextCase + if (nextCase == nil) then + repl = repl .. replRest + elseif (nextCase == true) then + --repl = repl .. toUpper(replRest) + repl = repl .. replRest + else + --repl = repl .. toLower(replRest) + repl = repl .. replRest + end + elseif (lastCase == true) then + -- lastCase was UPPER + if (nextCase == nil) then + repl = repl .. toUpper(replRest) + elseif (nextCase == true) then + repl = repl .. toUpper(replRest) + else + repl = repl .. replRest + end + else + -- lastCase was lower + if (nextCase == nil) then + repl = repl .. replRest + elseif (nextCase == true) then + --repl = repl .. toLower(replRest) + repl = repl .. replRest + else + --repl = repl .. toLower(replRest) + repl = repl .. replRest + end + end + return repl + end + + local pos = 1 + local result = text + local findText = result + local searchText = replTable.searchText + local replaceText = replTable.replaceText + local matchWhen = replTable.matchWhen + local doExactCase = replTable.exactCase + local doConsolidate = replTable.consolidate + local doStopOnMatch = replTable.stopOnMatch + local stopOnMatch = false + + if doExactCase then + self:DebugPrint("ReplaceCharacters : \"%s => %s\" (exact case)", searchText, replaceText) + else + self:DebugPrint("ReplaceCharacters : \"%s => %s\" (ignoreCase)", searchText, replaceText) + searchText = convertToCaseInsensitivePattern(searchText) + end + + local pos1, pos2, cap1, cap2, cap3, cap4, cap5, cap6, cap7, cap8, cap9 = find(findText, searchText, pos) + self:TracePrint("ReplaceCharacters : pos1: %d, pos2: %d", pos1, pos2) + self:TracePrint("ReplaceCharacters : cap1: %s, cap2: %s, cap3: %s, cap4: %s, cap5: %s, cap6: %s, cap7: %s, cap8: %s, cap9: %s", cap1, cap2, cap3, cap4, cap5, cap6, cap7, cap8, cap9) + while (pos1 ~= nil) and (pos2 ~= nil) and (pos1 <= pos2) do + -- continue from that position later + pos = pos2 + 1 + + if doStopOnMatch then + stopOnMatch = true + end + + local pre = sub(result, 1, pos1 - 1) + local match = sub(result, pos1, pos2) + local post = sub(result, pos2 + 1) + local wordStart = sub(pre, -1, -1) + local wordEnd = sub(post, 1, 1) + + -- additional checks for word boundaries + local doesMatchWhen = false + if (matchWhen == 2) then + -- replace always + doesMatchWhen = true + elseif (matchWhen == 3) then + -- replace only as a whole word + if (nilOrEmpty(wordStart) or (find(wordStart,"[%s%p]") ~= nil)) and (nilOrEmpty(wordEnd) or (find(wordEnd, "[%s%p]") ~= nil)) then + doesMatchWhen = true + end + elseif (matchWhen == 4) then + -- replace only at start + if (nilOrEmpty(wordStart) or (find(wordStart,"[%s%p]") ~= nil)) then + doesMatchWhen = true + end + elseif (matchWhen == 5) then + -- replace only at end + if (nilOrEmpty(wordEnd) or (find(wordEnd, "[%s%p]") ~= nil)) then + doesMatchWhen = true + end + elseif (matchWhen == 6) then + -- replace only at start or end + if (nilOrEmpty(wordStart) or (find(wordStart, "[%s%p]") ~= nil) or nilOrEmpty(wordEnd) or (find(wordEnd, "[%s%p]") ~= nil)) then + doesMatchWhen = true + end + elseif (matchWhen == 7) then + -- replace only in the middle + if (not nilOrEmpty(wordStart) and (find(wordStart, "[%w]") ~= nil) and not nilOrEmpty(wordEnd) and (find(wordEnd, "[%w]") ~= nil)) then + doesMatchWhen = true + end + end + + if (doesMatchWhen) then + -- replace substitutions + self:TracePrint("ReplaceCharacters : pre: %s, match: %s, post: %s, repl: %s", pre, match, post, replaceText) + local caps = { cap1, cap2, cap3, cap4, cap5, cap6, cap7, cap8, cap9 } + local replText = replaceCaptures(match, replaceText, caps) + tInsert(replacedTexts[replName], replText) + self:DebugPrint("ReplaceCharacters : pre: %s, match: %s, post: %s, repl: %s", pre, match, post, replText) + + if (not doExactCase) then + local repl, lastCase = "", nil + local c, matchRest = getNextCharUtf8(match) + local r, replRest = "", replText + + while (c ~= nil) do + r, replRest = getNextCharUtf8(replRest) + r = r or "" + replRest = replRest or "" + self:TracePrint("ReplaceCharacters : c: %s, rest: %s", c, matchRest) + self:TracePrint("ReplaceCharacters : r: %s, rest: %s", r, replRest) + + if (isUpper(c)) then + -- UPPER-CASE letter + self:TracePrint("ReplaceCharacters : characters: %s => %s", c, toUpper(r)) + lastCase = true + repl = repl .. toUpper(r) + elseif (isLower(c)) then + -- lower_case letter + self:TracePrint("ReplaceCharacters : characters: %s => %s", c, toLower(r)) + lastCase = false + repl = repl .. toLower(r) + else + -- no letter + self:TracePrint("ReplaceCharacters : characters: %s => %s", c, r) + lastCase = nil + repl = repl .. r + end + + c, matchRest = getNextCharUtf8(matchRest) + end + + self:TracePrint("ReplaceCharacters : remaining length %d", length(replRest)) + if (length(replRest) > 0) then + local nextLetter, _ = getNextCharUtf8(post) + nextLetter = nextLetter or "" + + local nextCase = nil + if (isUpper(nextLetter)) then + nextCase = true + elseif (isLower(nextLetter)) then + nextCase = false + end + + self:TracePrint("ReplaceCharacters : rest: %s, lastCase: %s, nextLetter: %s, nextCase: %s", replRest, lastCase, nextLetter, nextCase) + + repl = repl .. applyCase(replRest, lastCase, nextCase) + end + + replText = repl + end + + -- actual replacement + result = pre .. replText .. post + self:TracePrint("ReplaceCharacters : result: %s", result) + + -- remember positions for consolidate + if doConsolidate then + tInsert(consolidate[replName], pos1) + end + + -- update previous consolidate markers + local diff = lengthUtf8(replText) - lengthUtf8(match) + self:TracePrint("ReplaceCharacters : diff = %d - %d", lengthUtf8(replText), lengthUtf8(match)) + for key, posList in pairs(consolidate) do + if (key ~= replName) then + for i, pc in ipairs(posList) do + if (pos1 < pc) then + consolidate[key][i] = consolidate[key][i] + diff + end + end + end + end + + -- replacement text can lengthen or shorten the result + -- after replacement text and lowerText can have different sizes + pos = pos + diff + else + self:DebugPrint("ReplaceCharacters : does not match when: %d", matchWhen) + end + + findText = result + -- update values for next iteration + pos1, pos2, cap1, cap2, cap3, cap4, cap5, cap6, cap7, cap8, cap9 = find(findText, searchText, pos) + end + + if (text ~= result) then + self:DebugPrint("ReplaceCharacters : replaced \"%s\" with \"%s\" %s", text, result, stopOnMatch and "(stop)" or "") + end + if (matchWhen > 1) and doConsolidate then + self:DebugPrint("ReplaceCharacters : consolidate[" .. replName .. "] is:") + self:DebugPrint(consolidate[replName]) + end + + return result, stopOnMatch +end + +--- Replaces all character occurrences for which replacements have been defined in the options +-- @param text string +-- @param replacements table of mappings +-- @return string +function Grichelde:ReplaceAndConsolidate(text, replacements) + local replacements = replacements or self.db.profile.replacements or {} + self:TracePrint("ReplaceAndConsolidate : replacements") + self:TracePrint(replacements) + + local result = text + local consolidate = {} + local replacedTexts = {} + local stopOnMatch = nil + + -- replacements are done first + for replName, replTable in spairs(replacements) do + local searchText = replTable.searchText + if (not nilOrEmpty(searchText) and (replTable.matchWhen > 1)) then + consolidate[replName] = {} + replacedTexts[replName] = {} + + local stop = false + result, stop = self:ReplaceCharacters(result, replName, replTable, consolidate, replacedTexts) + + if stop then + stopOnMatch = replName + self:DebugPrint("ReplaceAndConsolidate : Stopping followup replacements after %s", replName) + break + end + else + -- empty mapping or never matched + self:DebugPrint("ReplaceAndConsolidate : Skip replacement %s", replName) + end + end + + -- consolidation is done last + for replName, replTable in spairs(replacements) do + local before = result + local search = replTable.searchText + + if (not nilOrEmpty(search) and (replTable.matchWhen > 1)) then + local lowerResult = toLower(result) + local offset = 0 + + if (replTable.consolidate) then + self:DebugPrint("consolidate[" .. replName .. "] is:") + self:DebugPrint(consolidate[replName]) + self:DebugPrint("replacedTexts[" .. replName .. "] is:") + self:DebugPrint(replacedTexts[replName]) + + for i, pos1 in spairs(consolidate[replName]) do + local replText = replacedTexts[replName][i] + self:DebugPrint("ReplaceAndConsolidate : consolidating \"%s => %s\"", search, replText) + + local pos2 = pos1 + length(replText) - 1 + self:TracePrint("ReplaceAndConsolidate : pos1: %d, pos2: %d", pos1, pos2) + local match = toLower(replText) + local next = sub(lowerResult, pos2 + 1, pos2 + 1 + pos2 - pos1) + self:TracePrint("ReplaceAndConsolidate : match: %s, next: %s", match, next) + + local _, p2 = find(next, "^" .. match) + self:TracePrint("ReplaceAndConsolidate : p2: %d", p2) + if (p2 ~= nil) then + result = sub(result, 1, pos2 + offset) .. sub(result, pos2 + 1 + p2 + offset) + + -- consolidation will shorten the resulting text + offset = offset + length(result) - length(lowerResult) + end + self:DebugPrint("ReplaceAndConsolidate : result: %s", result) + end + end + + if (before ~= result) then + self:DebugPrint("ReplaceAndConsolidate : consolidate \"%s\" with \"%s\"", before, result) + end + else + self:DebugPrint("ReplaceAndConsolidate : Skip consolidation for %s", replName) + end + + if (stopOnMatch ~= nil) and (stopOnMatch == replName) then + break + end + end + + self:DebugPrint("ReplaceAndConsolidate : final text:", result) + return result +end + +--- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures, raid target icons, +--- emotes, ooc or %-substitutons and returns the end location of the match, or 0 if no pattern was found +-- @param text string +-- @param currentChar string(1) current character (first one) of the text, given for performance reasons +-- @param previousChar string(1) previous character of the text, otherwise unreachable +-- @param preserveEmotes boolean ignore replacements for emotes, for testing purposes +-- @return number +function Grichelde:CheckForPreversableText(text, currentChar, previousChar, replaceEmotes) + self:DebugPrint("CheckForPreversableText : text:", text) + local replaceEmotes = replaceEmotes or self.db.profile.channels.emote or false + + -- Calling find on ever pattern might be inefficient but its way less code than marching over every character + if (currentChar == "|" ) then + for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.LINKS) do + local pos1, pos2 = find(text, "^" .. pattern) + if (pos1 == 1) and (pos2 ~= nil) then + self:DebugPrint("CheckForPreversableText : Found link or texture pattern \"%s\" at (%d, %d)", pattern, pos1, pos2) + return pos2 + end + end + end + + -- emote detection + if (currentChar == "*" or currentChar == "<") then + for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.EMOTES) do + local pos1, pos2 = find(text, "^" .. pattern) + if (pos1 == 1) and (pos2 ~= nil) then + local emote = sub(text, pos1, pos2) + if (replaceEmotes) then + self:DebugPrint("CheckForPreversableText : Found emote \"%s\" but preserved it", emote) + return pos2 + else + self:DebugPrint("CheckForPreversableText : Found emote \"%s\" at (%d, %d)", emote, pos1, pos2) + end + end + end + end + + local lowerText = toLower(text) + + -- %-substitutions + if (currentChar == "%") then + for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.SUBSTITUTES) do + local pos1, pos2 = find(lowerText, "^" .. pattern) + if (pos1 == 1) and (pos2 ~= nil) then + self:DebugPrint("CheckForPreversableText : Found substitute pattern \"%s\" at (%d, %d)", pattern, pos1, pos2) + return pos2 + end + end + end + + -- raid target markers + if (currentChar == "{") then + -- rt1-9 + local pattern = Grichelde.IGNORE_PATTERNS.RAID_TARGETS[1] + local pos1, pos2 = find(lowerText, "^" .. pattern) + if (pos1 == 1) and (pos2 ~= nil) then + self:DebugPrint("CheckForPreversableText : Found raid target marker \"%s\" at (%d, %d)", pattern, pos1, pos2) + return pos2 + end + + for _, localizedRT in ipairs(Grichelde.IGNORE_PATTERNS.LOCALIZED_RAID_TARGETS) do + local translation = toLower(self.L["IgnorePattern_" .. localizedRT]) + local localizedPattern = "^{" .. translation .. "}" + self:TracePrint("CheckForPreversableText : localizedPattern:", localizedPattern) + local pos1, pos2 = find(lowerText, localizedPattern) + if (pos1 == 1) and (pos2 ~= nil) then + self:DebugPrint("CheckForPreversableText : Found localized raid target marker \"%s\" at (%d, %d)", localizedPattern, pos1, pos2) + return pos2 + end + end + end + + -- ooc bracket detection + if (currentChar == "(") then + for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS.OOC_BRACKETS) do + local pos1, pos2 = find(lowerText, "^" .. pattern) + if (pos1 == 1) and (pos2 ~= nil) then + self:DebugPrint("CheckForPreversableText : Found ooc pattern \"%s\" at (%d, %d)", pattern, pos1, pos2) + return pos2 + end + end + end + + -- ooc without brackets: remaing text is treated as ooc completely! + if (currentChar == "o") then + local pattern = Grichelde.IGNORE_PATTERNS.OOC_NO_BRACKETS[1] + if ((previousChar == nil) or (find(previousChar, "%s") ~= nil)) and (find(lowerText, pattern) ~= nil) then + self:DebugPrint("CheckForPreversableText : ooc for remaing text") + return length(text) + 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 the text to apply the mappings on +-- @param preserveEmotes boolean ignore replacements for emotes, for testing purposes +-- @return string +function Grichelde:ReplaceText(text, replacements, replaceEmotes) + local lookAheads = { '|', '*', '<', '%', '{', '(', 'o' } + local newText = text + local preserveEmotes = replaceEmotes or self.db.profile.channels.emote or false + local replacements = replacements or self.db.profile.replacements or {} + local finalText = "" + + local currentChar, previousChar + local current = 1 + local lastStart = 1 + + -- no UTF-8 support required here, as the positions are used + while current <= length(newText) do + previousChar = currentChar + currentChar = sub(newText, current, current) + self:TracePrint("ReplaceText : current/char : %s,%s", current, currentChar) + + -- as there is not OR in Luas pattern matching, search for all of the exclude patterns after another is + -- cumbersome and inefficient -> look for each char consecutively if it matches the starting pattern only + -- and if if matches do full pattern matching + if (not tContains(lookAheads, currentChar)) then + current = current + 1 + else + -- lookahead-check for all preservable patterns (itemLinks, textures, emotes, ooc, etc.) + local textAhead = sub(newText, current) + local posEnd = self:CheckForPreversableText(textAhead, currentChar, previousChar, preserveEmotes) + if (posEnd > 0) then + self:DebugPrint("ReplaceText : Found an ignore pattern") + + -- split text and continue after preserved text + local textBefore = sub(newText, lastStart, current - 1) + local replacement = self:ReplaceAndConsolidate(textBefore, replacements) + 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 + + -- catchup remaining text to the end + local remainingText = sub(newText, lastStart) + local replacement = self:ReplaceAndConsolidate(remainingText, replacements) + finalText = finalText .. replacement + + self:DebugPrint("ReplaceText : replaced \"%s\"", text) + self:DebugPrint("ReplaceText : with \"%s\"", finalText) + return finalText +end + +function Grichelde:IsOneBigEmote(text) + local firstWord, _ = self:SplitOnFirstMatch(text) assert(firstWord ~= nil, "firstWord is never nil") -- emote detection - local isOneBigEmote = false + local isEmote = false -- scheme *emote* - if sub(firstWord, 1, 1) == "<" then + if (sub(firstWord, 1, 1) == "<") then -- search for emote end local _, emoteEnd = find(text, "%>", 2) - isOneBigEmote = (emoteEnd == length(text)) + isEmote = (emoteEnd == length(text)) end - if not isOneBigEmote and sub(firstWord, 1, 1) == "*" then + if (not isEmote and (sub(firstWord, 1, 1) == "*")) then -- search for emote end local _, emoteEnd = find(text, "%*", 2) - isOneBigEmote = (emoteEnd == length(text)) + isEmote = (emoteEnd == length(text)) end -- scheme **emote** - if not isOneBigEmote and sub(firstWord, 1, 2) == "**" then + if (not isEmote and (sub(firstWord, 1, 2) == "**")) then -- search for emote end local _, emoteEnd = find(text, "%*%*", 3) - isOneBigEmote = (emoteEnd == length(text)) + isEmote = (emoteEnd == length(text)) end -- the whole text is one big emote - return isOneBigEmote + return isEmote +end + +function Grichelde:ConvertBlizzTypeToOption(channel) + local option = tFilter(self.BLIZZ_TYPE_TO_OPTIONS, + function(_, k, _) return channel == k end) + if (not tIsEmpty(option)) then + self:DebugPrint("ConvertBlizzTypeToOption : convert %s to %s", channel, option[1]) + return option[1] + else + return nil + end end --- Checks if a message can be replaced according to configuration. @@ -107,7 +622,7 @@ function Grichelde:CheckReplacementAllowed(text, channel) end -- skip if no text - if nilOrEmpty(text) then + if (nilOrEmpty(text)) then return false end @@ -120,7 +635,7 @@ function Grichelde:CheckReplacementAllowed(text, channel) self:DebugPrint("CheckReplacementAllowed : allowed channels:") self:DebugPrint(allowedChannels) - if tIsEmpty(allowedChannels) then + if (tIsEmpty(allowedChannels)) then self:DebugPrint("CheckReplacementAllowed : skip channel type:", chan) return false end @@ -129,13 +644,13 @@ function Grichelde:CheckReplacementAllowed(text, channel) assert(firstWord ~= nil, "firstWord is never nil") -- don't replace slash commands - if sub(firstWord, 1, 1) == "/" then + if (sub(firstWord, 1, 1) == "/") then self:DebugPrint("CheckReplacementAllowed : skip other slash commands:", firstWord) return false end -- emote detection - if IsOneBigEmote(self, text) then + if (self:IsOneBigEmote(text)) then self:DebugPrint("CheckReplacementAllowed : one big emote") return self.db.profile.channels.emote end @@ -146,500 +661,155 @@ end --- Checks if the text from the Grichelde slash command can be replaced and if so --- returns the replacable text, the chat type and target (player or channel) from the message text. ---- This is used the the override slash command: "/gri /emote text to replace". --- @param text (string) the whole message +--- self is used the the override slash command: "/gri /emote text to replace". +-- @param message (string) the whole message -- @return message (string) stripped message -- @return type (string) chat type -- @return channel (string|number) channel number for whispers -function Grichelde:CheckAndExtractMessageTypeTarget(text) - self:DebugPrint("CheckAndExtractMessageTypeTarget : text:", text) +function Grichelde:CheckAndExtractMessageTypeTarget(message) + self:DebugPrint("CheckAndExtractMessageTypeTarget : text:", message) -- skip if no further text - if nilOrEmpty(text) then - return nil -- dont send text at all + if (nilOrEmpty(message)) then + -- dont send text at all + return nil end -- first word should be a chat command - if sub(text, 1, 1) == "/" then + if (sub(message, 1, 1) == "/") then -- extract chat command - local chatCmd, targetAndText = self:SplitOnFirstMatch(text) + local chatCmd, targetAndText = self:SplitOnFirstMatch(message) assert(chatCmd ~= nil, "chatCmd is never nil") local type = tFilter(self.SUPPORTED_CHAT_COMMANDS, function(_, k, _) return chatCmd == k end ) - self:DebugPrint("CheckAndExtractMessageTypeTarget : determined type:") - self:DebugPrint(type) + assert(#type < 2) + + if (not tIsEmpty(type)) then + self:DebugPrint("CheckAndExtractMessageTypeTarget : determined type: %s", type[1]) - if not tIsEmpty(type) then -- valid /chattype - if type[1] == "WHISPER" then + if (type[1] == "WHISPER") then -- special reply handling - if "/r" == chatCmd or "/reply" == chatCmd then + if ("/r" == chatCmd) or ("/reply" == chatCmd) then -- reuse last type and target if possible local lastTold, lastToldType = ChatEdit_GetLastToldTarget() self:DebugPrint("CheckAndExtractMessageTypeTarget : lastTell, lastTellType =", lastTold, lastToldType) - return targetAndText, lastToldType or type[1], lastTold - elseif "/tt" == chatCmd then + return targetAndText, lastToldType or "WHISPER", lastTold + elseif ("/tt" == chatCmd) then -- determine target from game world selection if (not UnitExists("target") or not UnitIsPlayer("target")) then self:ErrorPrint(self.L.Error_InvalidWhisperTarget) - return nil -- dont send text at all + -- dont send text at all + return nil end local target = UnitName("target"); - if target == nil then + if (target == nil) then self:ErrorPrint(self.L.Error_InvalidWhisperTarget) - return nil -- dont send text at all + -- dont send text at all + return nil end -- eventually we found our target self:DebugPrint("CheckAndExtractMessageTypeTarget : target:", target) - return targetAndText, type[1], target + return targetAndText, "WHISPER", target else -- determine target from text - local target, msg = self:SplitOnFirstMatch(targetAndText) - if target == nil then + local target, text = self:SplitOnFirstMatch(targetAndText) + if (target == nil) then self:ErrorPrint(self.L.Error_InvalidWhisperTarget) - return nil -- dont send text at all + -- dont send text at all + return nil end -- eventually we found our target self:DebugPrint("CheckAndExtractMessageTypeTarget : determined target:", target) - return msg, type[1], target + return text, "WHISPER", target end else -- all other chat types return targetAndText, type[1], nil end else + self:DebugPrint("CheckAndExtractMessageTypeTarget : not a standard channel: %s", chatCmd) + -- if not a valid chat command, try as a numbered channel local _, _, channelNumber = find(chatCmd, "^/(%d+)") - if channelNumber ~= nil then + if (channelNumber ~= nil) then local channelId = GetChannelName(channelNumber) - if channelId ~= nil then + if (channelId ~= nil) then return targetAndText, "CHANNEL", channelId end end -- ignore any other slash commands self:ErrorPrint(self.L.Error_InvalidChannel) - return nil -- dont send text at all + -- dont send text at all + return nil end - elseif IsOneBigEmote(self, text) then + elseif self:IsOneBigEmote(message) then self:DebugPrint("CheckAndExtractMessageTypeTarget : determined EMOTE type") - return text, "EMOTE", nil + return message, "EMOTE", nil else -- in any other case, treat as ordinary text, assume default type and channel - return text + return message end end -function Grichelde:ConvertBlizzTypeToOption(channel) - local option = tFilter(self.BLIZZ_TYPE_TO_OPTIONS, - function(_, k, _) return channel == k end - ) - if not tIsEmpty(option) then - self:DebugPrint("ConvertBlizzTypeToOption : convert %s to %s", channel, option[1]) - return option[1] - else - return nil +function Grichelde:CleanseMessage(message) + local text = message or "" + if (IsAddOnLoaded("Misspelled")) then + self:DebugPrint("Misspelled detected: cleansing message") + text = _G.Misspelled:RemoveHighlighting(message) end + return trim(text) 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 lookAheads = {'|', '{', '%', '*', '<', '(', 'o'} - local newText = text - local finalText = "" +--- Always replaces the text accoording to the configuration, even if activation or channel was disabled. +--- self is used the the override slash command: "/gri /emote text to replace". +--- NOTE: type and channel (in case of whispers) are determined from the message text. +-- @param message string +-- @param type string +function Grichelde:SendChatMessageOverride(message, ...) + local cleasendText = self:CleanseMessage(message) - local currentChar, previousChar - local current = 1 - local lastStart = 1 + local fallbackType, fallbackLang = DEFAULT_CHAT_FRAME.editBox.chatType or "SAY", DEFAULT_CHAT_FRAME.editBox.languageID + local msg, type, chan = self:CheckAndExtractMessageTypeTarget(cleasendText) - while current <= length(newText) do - previousChar = currentChar - currentChar = sub(newText, current, current) - self:TracePrint("current/char : %s,%s", current, currentChar) + if (msg ~= nil) then + msg = self:ReplaceText(msg) - if ( not tContains(lookAheads, currentChar)) then - current = current + 1 + if (type ~= nil) then + self:SendChunkifiedChatMessage(msg, type, fallbackLang, chan, ...) else - -- lookahead-check for all preservable patterns (itemLinks, textures, emotes, ooc, etc.) - local textAhead = sub(newText, current) - local posEnd = self:CheckForPreversableText(textAhead, previousChar) - 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 \"%s\"", text) - self:DebugPrint("ReplaceText : with \"%s\"", finalText) - return finalText -end - - ---- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures, raid target icons, ---- emotes, ooc or %-substitutons and returns the end location of the match, or 0 if no pattern was found --- @param text string --- @return number -function Grichelde:CheckForPreversableText(text, previousChar) - self:TracePrint("CheckForPreversableText : text:", text) - - -- Calling find on ever pattern might be inefficient but its way less code than marching over every character - for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS_CASE_SENSITIVE) 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 + self:SendChunkifiedChatMessage(msg, fallbackType, fallbackLang, chan, ...) end + else + -- suppress invalid messages/channels/targets end - - -- emote detection - for _, pattern in ipairs(Grichelde.EMOTE_PATTERNS) do - local pos1, pos2 = find(text, pattern) - if pos1 == 1 and pos2 ~= nil then - local emote = sub(text, pos1, pos2) - if (not self.db.profile.channels.emote) then - self:DebugPrint("CheckForPreversableText : Found emote \"%s\" but preserved it", emote) - return pos2 - else - self:DebugPrint("CheckForPreversableText : Found emote \"%s\" at (%d, %d)", emote, pos1, pos2) - end - end - end - - -- %-substitutions - local lowerText = toLower(text) - for _, pattern in ipairs(Grichelde.IGNORE_PATTERNS_CASE_INSENSITIVE) do - local pos1, pos2 = find(lowerText, 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 - - -- Localized raid target markers - for _, localizedRT in ipairs(Grichelde.LOCALIZED_RAID_TARGETS) do - local translation = toLower(self.L["IgnorePattern_" .. localizedRT]) - local locPattern = "{" .. translation .. "}" - self:TracePrint("CheckForPreversableText : locPattern:", locPattern) - local pos1, pos2 = find(lowerText, locPattern) - if pos1 == 1 and pos2 ~= nil then - self:DebugPrint("CheckForPreversableText : Found localized raid target marker \"%s\" at (%d, %d)", locPattern, pos1, pos2) - return pos2 - end - end - - -- ooc detection remaing text is treated as ooc completely! - if (previousChar == nil or previousChar == ' ') and find(lowerText, "^ooc[%:%s]") then - self:DebugPrint("CheckForPreversableText : ooc for remaing text") - return length(text) - end - - self:DebugPrint("CheckForPreversableText : no ignore pattern found") - return 0 end ---- Replaces all character occurrences for which replacements have been defined in the options --- @param text string --- @return string -function Grichelde:ReplaceCharacters(text) - local replacements = self.db.profile.replacements or {} - self:DebugPrint("ReplaceCharacters : replacements") - self:DebugPrint(replacements) - - local result = text - local consolidate = {} - local stopOnMatch = nil - - -- replacements are done first - for replName, replTable in spairs(replacements) do - local before = result - local search = replTable.searchText - - if not nilOrEmpty(search) and replTable.active then - local replace = replTable.replaceText - consolidate[replName] = {} - - if replTable.exactCase then - -- exact case - self:DebugPrint("ReplaceCharacters : \"%s => %s\" (exact case)", search, replace) - local pos, offset = 1, 0 - local oldResult = result - - local pos1, pos2 = find(oldResult, search, pos) - self:TracePrint("ReplaceCharacters : pos1: %d, pos2: %d", pos1, pos2) - while (pos1 and pos2 and pos1 <= pos2) do - if replTable.stopOnMatch and stopOnMatch == nil then - stopOnMatch = replName - end - - local pre = sub(result, 1, pos1 - 1 + offset) - local post = sub(result, pos2 + 1 + offset) - self:TracePrint("ReplaceCharacters : pre: %s, post: %s", pre, post) - - -- actual replacement - result = pre .. replace .. post - self:DebugPrint("result: %s", result) - - -- remember positions for consolidate - if replTable.consolidate then - tInsert(consolidate[replName], pos1 + offset) - end - - -- update previous consolidate markers - local diff = length(replace) - length(search) - for key, posList in pairs(consolidate) do - if key ~= replName then - for i, pc in ipairs(posList) do - if pos1 < pc then - consolidate[key][i] = consolidate[key][i] + diff - end - end - end - end - - -- replacement text can lengthen or shorten the resulting text - -- after replacement result and lowerResult can have different sizes - offset = offset + diff - -- update values for next iteration - pos = pos2 + 1 - pos1, pos2 = find(oldResult, search, pos) - end - else - self:DebugPrint("ReplaceCharacters : \"%s => %s\" (ignoreCase)", search, replace) - local pos, offset = 1, 0 - local lowerResult = toLower(result) - local lowerSearch = toLower(search) - - local pos1, pos2 = find(lowerResult, lowerSearch, pos) - self:TracePrint("ReplaceCharacters : pos1: %d, pos2: %d", pos1, pos2) - while (pos1 and pos2 and pos1 <= pos2) do - if replTable.stopOnMatch and stopOnMatch == nil then - stopOnMatch = replName - end - - local pre = sub(result, 1, pos1 - 1 + offset) - local match = sub(result, pos1 + offset, pos2 + offset) - local post = sub(result, pos2 + 1 + offset) - self:TracePrint("ReplaceCharacters : pre: %s, match: %s, post: %s", pre, match, post) - - -- keep cases - local utf8, uc, tc = nil, 0, 0 - local repl = "" - local lastCase = nil - for p = pos1, pos2 do - self:TracePrint("ReplaceCharacters : p: %d", p) - local c = sub(match, p - pos1 + 1, p - pos1 + 1) - -- put together umlaut or accent - if utf8 ~= nil then - c = utf8 .. c - utf8 = nil - end - - -- if not umlaut or accent - if c ~= "\195" then - local r = sub(replace, p - pos1 + 1 - uc + tc, p - pos1 + 1 - uc + tc) or "" - if r == "\195" then - r = sub(replace, p - pos1 + 1 - uc + tc, p - pos1 + 1 - uc + tc + 1) or "" - tc = tc + 1 - end - self:TracePrint("ReplaceCharacters : character: %s, %s", c, r) - - if (isUpper(c)) then -- UPPER-CASE letter - lastCase = true - repl = repl .. toUpper(r) - elseif (isLower(c)) then -- lower_case letter - lastCase = false - repl = repl .. toLower(r) - else -- no letter - lastCase = nil - repl = repl .. r - end - else - -- handle UTF8 characters - utf8 = c - uc = uc + 1 - end - end - - self:TracePrint("ReplaceCharacters : length %d > %d", length(replace), pos2 - pos1 + 1 - uc + tc) - if (length(replace) > pos2 - pos1 + 1 - uc + tc) then - local remainingReplace = sub(replace, pos2 - pos1 + 2 - uc + tc) - local nextLetter = sub(post, 1, 1) - self:TracePrint("ReplaceCharacters : rest: %s, n: %s, lastCase: %s", remainingReplace, nextLetter, lastCase) - - if lastCase == nil then - if (isUpper(nextLetter)) then - repl = repl .. toUpper(remainingReplace) - else - repl = repl .. remainingReplace - end - elseif lastCase == false then - repl = repl .. remainingReplace - else - if (isLower(nextLetter)) then - repl = repl .. toLower(remainingReplace) - else - repl = repl .. toUpper(remainingReplace) - end - end - end - - -- actual replacement - result = pre .. repl .. post - self:DebugPrint("ReplaceCharacters : result: %s", result) - - -- remember positions for consolidate - if replTable.consolidate then - tInsert(consolidate[replName], pos1 + offset) - self:TracePrint("consolidate[" .. replName .. "] is:") - self:TracePrint(consolidate[replName]) - end - - -- update previous consolidate markers - local diff = length(repl) - length(lowerSearch) - for key, posList in pairs(consolidate) do - if key ~= replName then - for i, pc in ipairs(posList) do - if pos1 < pc then - consolidate[key][i] = consolidate[key][i] + diff - end - end - end - end - - -- replacement text can be longer or shorter the resulting text - -- after replacement result and lowerResult can have different sizes - offset = offset + diff - -- update values for next iteration - pos = pos2 + 1 - pos1, pos2 = find(lowerResult, lowerSearch, pos) - end - end - - if before ~= result then - self:DebugPrint("ReplaceCharacters : replaced \"%s\" with \"%s\"", before, result) - end - if replTable.consolidate then - self:DebugPrint("consolidate[" .. replName .. "] is:") - self:DebugPrint(consolidate[replName]) - end - else - self:DebugPrint("ReplaceCharacters : Skip replacement for %s", replName) - end - - if stopOnMatch ~= nil then - break - end - end - - -- consolidation is done last - for replName, replTable in spairs(replacements) do - local before = result - local search = replTable.searchText - - if not nilOrEmpty(search) and replTable.active then - local replace = replTable.replaceText - local lowerResult = toLower(result) - local offset = 0 - - if replTable.consolidate then - self:DebugPrint("ReplaceCharacters : consolidating \"%s => %s\"", search, replace) - self:DebugPrint("consolidate[" .. replName .. "] is:") - self:DebugPrint(consolidate[replName]) - - for _, pos1 in spairs(consolidate[replName]) do - local pos2 = pos1 + length(replace) - 1 - self:TracePrint("ReplaceCharacters : pos1: %d, pos2: %d", pos1, pos2) - local match = toLower(replace) - local next = sub(lowerResult, pos2 + 1, pos2 + 1 + pos2 - pos1) - self:TracePrint("ReplaceCharacters : match: %s, next: %s", match, next) - - local _, p2 = find(next, "^" .. match) - self:TracePrint("ReplaceCharacters : p2: %d", p2) - if (p2) then - result = sub(result, 1, pos2 + offset) .. sub(result, pos2 + 1 + p2 + offset) - - -- consolidation will shorten the resulting text - offset = offset + length(result) - length(lowerResult) - end - self:DebugPrint("ReplaceCharacters : result: %s", result) - end - end - - if before ~= result then - self:DebugPrint("ReplaceCharacters : consolidate \"%s\" with \"%s\"", before, result) - end +--- 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, ...) + if (not self.db.profile.enabled) then + self:DebugPrint("SendChatMessage : disabled") + self.hooks["SendChatMessage"](message, type, ...); + elseif (nilOrEmpty(message)) then + self:DebugPrint("SendChatMessage : no text") + self.hooks["SendChatMessage"](message, type, ...); + else + local cleasendText = self:CleanseMessage(message) + if (self:CheckReplacementAllowed(cleasendText, type)) then + cleasendText = self:ReplaceText(cleasendText) + self:SendChunkifiedChatMessage(cleasendText, type, ...) else - self:DebugPrint("ReplaceCharacters : Skip consolidation for %s", replName) - end - - if stopOnMatch == replName then - break - end - end - - self:DebugPrint("ReplaceCharacters : final text:", result) - return result -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 or "") - - 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 + self.hooks["SendChatMessage"](message, type, ...); 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 +end \ No newline at end of file diff --git a/GricheldeConstants.lua b/GricheldeConstants.lua index e53196b..cd58e46 100644 --- a/GricheldeConstants.lua +++ b/GricheldeConstants.lua @@ -1,45 +1,46 @@ -- read namespace from global env local _G = _G -local Grichelde = _G.Grichelde +local Grichelde = _G.Grichelde or {} -- constants and upvalues -Grichelde.LOG_LEVEL = {} -Grichelde.LOG_LEVEL.DEBUG = 1 -Grichelde.LOG_LEVEL.TRACE = 2 +Grichelde.LOG_LEVEL = { DEBUG = 1, TRACE = 2 } Grichelde.MAPPING_OFFSET = 10 Grichelde.MINIMAP_ENABLED = 1.0 Grichelde.MINIMAP_DARKENDED = 0.5 -Grichelde.ICONS = {} -Grichelde.ICONS.MOVE_UP = "Interface\\MainMenuBar\\UI-MainMenu-ScrollUpButton-Up" -Grichelde.ICONS.MOVE_UP_DISABLED = "Interface\\MainMenuBar\\UI-MainMenu-ScrollUpButton-Disabled" -Grichelde.ICONS.MOVE_DOWN = "Interface\\MainMenuBar\\UI-MainMenu-ScrollDownButton-Up" -Grichelde.ICONS.MOVE_DOWN_DISABLED = "Interface\\MainMenuBar\\UI-MainMenu-ScrollDownButton-Disabled" -Grichelde.ICONS.DELETE = "Interface\\Buttons\\UI-Panel-MinimizeButton-Up" -Grichelde.ICONS.DELETE_DISABLED = "Interface\\Buttons\\UI-Panel-MinimizeButton-Disabled" +Grichelde.ICONS = { + MOVE_UP = "Interface\\MainMenuBar\\UI-MainMenu-ScrollUpButton-Up", + MOVE_UP_DISABLED = "Interface\\MainMenuBar\\UI-MainMenu-ScrollUpButton-Disabled", + MOVE_DOWN = "Interface\\MainMenuBar\\UI-MainMenu-ScrollDownButton-Up", + MOVE_DOWN_DISABLED = "Interface\\MainMenuBar\\UI-MainMenu-ScrollDownButton-Disabled", + DELETE = "Interface\\Buttons\\UI-Panel-MinimizeButton-Up", + DELETE_DISABLED = "Interface\\Buttons\\UI-Panel-MinimizeButton-Disabled", +} -- colors: -Grichelde.COLORS = {} -Grichelde.COLORS.NORMAL = _G.NORMAL_FONT_COLOR -Grichelde.COLORS.HIGHLIGHT = _G.HIGHLIGHT_FONT_COLOR -Grichelde.COLORS.RED = _G.RED_FONT_COLOR -Grichelde.COLORS.GREEN = _G.GREEN_FONT_COLOR - -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.LIGHTGRAY = "|cffC0C0C0" -Grichelde.COLOR_CODES.GRAY = _G.GRAY_FONT_COLOR_CODE or "|cff808080" -Grichelde.COLOR_CODES.DARKGRAY = "|cff404040" -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.COLORS = { + NORMAL = _G.NORMAL_FONT_COLOR, + HIGHLIGHT = _G.HIGHLIGHT_FONT_COLOR, + RED = _G.RED_FONT_COLOR, + GREEN = _G.GREEN_FONT_COLOR, +} + +Grichelde.COLOR_CODES = { + PREFIX = "|c00FFAA00", + -- https://github.com/stoneharry/Misc-WoW-Stuff/blob/master/EoC%20Interface/FrameXML/Constants.lua + NORMAL = _G.NORMAL_FONT_COLOR_CODE or "|cffffd200", + HIGHLIGHT = _G.HIGHLIGHT_FONT_COLOR_CODE or "|cffffffff", + RED = _G.RED_FONT_COLOR_CODE or "|cffff2020", + GREEN = _G.GREEN_FONT_COLOR_CODE or "|cff20ff20", + LIGHTGRAY = "|cffC0C0C0", + GRAY = _G.GRAY_FONT_COLOR_CODE or "|cff808080", + DARKGRAY = "|cff404040", + YELLOW = _G.YELLOW_FONT_COLOR_CODE or "|cffffff00", + LIGHTYELLOW = _G.LIGHTYELLOW_FONT_COLOR_CODE or "|cffffff9a", + ORANGE = _G.ORANGE_FONT_COLOR_CODE or "|cffff7f3f", + CLOSE = _G.FONT_COLOR_CODE_CLOSE or "|r", +} Grichelde.SLASH_COMMANDS = { "gri", "grichelde" } @@ -113,39 +114,52 @@ Grichelde.BLIZZ_TYPE_TO_OPTIONS = { } -- do not replace these patterns -Grichelde.IGNORE_PATTERNS_CASE_SENSITIVE = { - "|[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 - "|K.-|k", -- Battle.net - "|n", -- newline - - "%(%(.-%)%)", -- (( ... )) - "%(%s*ooc[%:%s].-%)", -- ( ooc ) +Grichelde.IGNORE_PATTERNS = { + LINKS = { + "|[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 + "|K.-|k", -- Battle.net + "|n", -- newline + }, + EMOTES = { + "%*.-%*", -- emotes * + "%*%*.-%*%*", -- emotes ** + "%<.-%>", -- emotes < > + }, + SUBSTITUTES = { + "%%n", -- player's name + "%%z", -- player's currnt zone + "%%sz", -- player's current sub-zone + "%%loc", -- player's map coordinates + "%%t", -- name of target + "%%f", -- name of focus target + "%%m", -- name of mouseover unit + "%%p", -- name of player pet + "%%tt", -- name of player's target's target + }, + RAID_TARGETS = { + "{rt[1-8]}", -- rumbered raid target icons, localized raid targets are handled differently + }, + LOCALIZED_RAID_TARGETS = { + "Star", + "Circle", + "Diamond", + "Triangle", + "Moon", + "Square", + "Cross", + "Skull", + }, + OOC_BRACKETS = { + "%(%(.-%)%)", -- (( ... )) + "%(%s*ooc[%:%s].-%)", -- ( ooc ) + }, + OOC_NO_BRACKETS = { + "ooc[%:%s]", + }, } --- for separate emote detection -Grichelde.EMOTE_PATTERNS = { - "%*.-%*", -- emotes * - "%*%*.-%*%*", -- emotes ** - "%<.-%>" -- emotes < > -} - -Grichelde.IGNORE_PATTERNS_CASE_INSENSITIVE = { - "{rt[1-8]}", -- rumbered raid target icons, localized raid targets are handled differently - "%%n", -- player's name - "%%z", -- player's currnt zone - "%%sz", -- player's current sub-zone - "%%loc", -- player's map coordinates - "%%t", -- name of target - "%%f", -- name of focus target - "%%m", -- name of mouseover unit - "%%p", -- name of player pet - "%%tt" -- name of player's target's target -} - -Grichelde.LOCALIZED_RAID_TARGETS = { "Star", "Circle", "Diamond", "Triangle", "Moon", "Square", "Cross", "Skull" } - local function nilOrEmpty(s) return s == nil or s:trim() == "" end @@ -154,22 +168,22 @@ local function spairs(t, orderFunc) -- collect the keys local sortedKeys = {} -- for every non-nil value - for key, _ in Grichelde.functions.pairs(t) do - Grichelde.functions.tInsert(sortedKeys, key) + for key, _ in Grichelde.F.pairs(t) do + Grichelde.F.tInsert(sortedKeys, key) end - if orderFunc then - Grichelde.functions.tSort(sortedKeys, function(a, b) return orderFunc(sortedKeys, a, b) end) + if (orderFunc ~= nil) then + Grichelde.F.tSort(sortedKeys, function(a, b) return orderFunc(sortedKeys, a, b) end) else -- lexicographical order - Grichelde.functions.tSort(sortedKeys) + Grichelde.F.tSort(sortedKeys) end -- return the iterator function local it = 0 return function() it = it + 1 - if sortedKeys[it] then + if (sortedKeys[it] ~= nil) then return sortedKeys[it], t[sortedKeys[it]] else return nil @@ -179,23 +193,23 @@ end local function tFilter(t, condition, extract) local filtered = {} - for key, value in Grichelde.functions.pairs(t) do + for key, value in Grichelde.F.pairs(t) do local cond = false - if condition then - local t = Grichelde.functions.type(condition) - if t == "function" then + if (condition) then + local t = Grichelde.F.type(condition) + if (t == "function") then cond = condition(t, key, value) - elseif t == "string" or t == "number" then + elseif (t == "string") or (t == "number") then cond = (value == condition) end end - if cond then + if (cond) then local val = value - if extract and Grichelde.functions.type(extract) == "function" then + if (extract and (Grichelde.F.type(extract) == "function")) then val = extract(t, key, value) end - Grichelde.functions.tInsert(filtered, val) + Grichelde.F.tInsert(filtered, val) end end return filtered @@ -203,9 +217,9 @@ end local function tSize(t) local size = 0 - if (t) then + if (t ~= nil) then -- for every non-nil value - for _, _ in Grichelde.functions.pairs(t) do + for _, _ in Grichelde.F.pairs(t) do size = size + 1 end end @@ -214,45 +228,117 @@ end local function tIsEmpty(t) if (not t) then return true end - return Grichelde.functions.tNext(t) == nil + return Grichelde.F.tNext(t) == nil end local function tClone(orig) - local orig_type = Grichelde.functions.type(orig) + local orig_type = Grichelde.F.type(orig) local copy - if orig_type == 'table' then + if (orig_type == 'table') then copy = {} -- for every non-nil value - for orig_key, orig_value in Grichelde.functions.tNext, orig, nil do + for orig_key, orig_value in Grichelde.F.tNext, orig, nil do copy[tClone(orig_key)] = tClone(orig_value) end - Grichelde.functions.setmetatable(copy, tClone(Grichelde.functions.getmetatable(orig))) + Grichelde.F.setmetatable(copy, tClone(Grichelde.F.getmetatable(orig))) else -- number, string, boolean, etc copy = orig end return copy end -local function isChar(word) - return Grichelde.functions.find(word, "[%z\65-\90\97-\122\195-\197][\128-\191]?") +local function getNextCharUtf8(word) + if ((word == nil) or (Grichelde.F.type(word) ~= "string") or (Grichelde.F.length(word) < 1)) then + return nil, nil + end + + local wordLength = Grichelde.F.length(word) + local c1 = Grichelde.F.toByte(word, 1) + if (c1 > 0) and (c1 <= 127) then + -- UTF8-1 + return Grichelde.F.bytes2Char(c1), Grichelde.F.sub(word, 2) + elseif (c1 >= 194) and (c1 <= 223) then + -- UTF8-2 + Grichelde.F.assert(wordLength >= 2, "broken UTF-8 character") + local c2 = Grichelde.F.toByte(word, 2) + return Grichelde.F.bytes2Char(c1, c2), Grichelde.F.sub(word, 3) + elseif (c1 >= 224) and (c1 <= 239) then + -- UTF8-3 + Grichelde.F.assert(wordLength >= 3, "broken UTF-8 character") + local c2 = Grichelde.F.toByte(word, 2) + local c3 = Grichelde.F.toByte(word, 3) + return Grichelde.F.bytes2Char(c1, c2, c3), Grichelde.F.sub(word, 4) + elseif (c1 >= 240) and (c1 <= 244) then + -- UTF8-4 + Grichelde.F.assert(wordLength >= 4, "broken UTF-8 character") + local c2 = Grichelde.F.toByte(word, 2) + local c3 = Grichelde.F.toByte(word, 3) + local c4 = Grichelde.F.toByte(word, 4) + return Grichelde.F.bytes2Char(c1, c2, c3, c4), Grichelde.F.sub(word, 5) + else + return nil, nil + end +end + +local function isLetter(word) + local char = Grichelde.F.getNextCharUtf8(word) + return (char ~= nil) and (Grichelde.F.toUpper(char) ~= Grichelde.F.toLower(char)) end local function isNumber(digit) - return Grichelde.functions.find(digit, "%d+") + if ((digit == nil) or (Grichelde.F.type(digit) ~= "string") or (Grichelde.F.length(digit) < 1)) then + return false + else + return Grichelde.F.find(digit, "%d+") ~= nil + end end local function isUpper(word) - return Grichelde.functions.isChar(word) and word == Grichelde.functions.toUpper(word) + if ((word == nil) or (Grichelde.F.type(word) ~= "string") or (Grichelde.F.length(word) < 1)) then + return false + elseif (Grichelde.F.toUpper(word) == Grichelde.F.toLower(word)) then + return false + else + return word == Grichelde.F.toUpper(word) + end end local function isLower(word) - return Grichelde.functions.isChar(word) and word == Grichelde.functions.toLower(word) + if ((word == nil) or (Grichelde.F.type(word) ~= "string") or (Grichelde.F.length(word) < 1)) then + return false + elseif (Grichelde.F.toUpper(word) == Grichelde.F.toLower(word)) then + return false + else + return word == Grichelde.F.toLower(word) + end end local function isCapital(word) - return Grichelde.functions.legnth(word) > 1 - and Grichelde.functions.isUpper(Grichelde.functions.sub(word,1,1)) - and Grichelde.functions.isLower(Grichelde.functions.sub(word,2)) + if ((word == nil) or (Grichelde.F.type(word) ~= "string") or (Grichelde.F.length(word) < 1)) then + return false + else + local first, rest = Grichelde.F.getNextCharUtf8(word) + local isCapital = Grichelde.F.isUpper(first) + if (rest ~= nil) then + return isCapital and Grichelde.F.isLower(rest) + else + return isCapital + end + end +end + +local function capitalize(word) + if ((word == nil) or (Grichelde.F.type(word) ~= "string") or (Grichelde.F.length(word) < 1)) then + return "" + else + local first, rest = Grichelde.F.getNextCharUtf8(word) + local capital = Grichelde.F.toUpper(first) + if (rest ~= nil) then + return capital .. Grichelde.F.toLower(rest) + else + return capital + end + end end local function color(color, text) @@ -260,74 +346,90 @@ local function color(color, text) end local function cPrefix(text) - return Grichelde.functions.color(Grichelde.COLOR_CODES.PREFIX, text) + return Grichelde.F.color(Grichelde.COLOR_CODES.PREFIX, text) end local function cYellow(text) - return Grichelde.functions.color(Grichelde.COLOR_CODES.NORMAL, text) + return Grichelde.F.color(Grichelde.COLOR_CODES.NORMAL, text) end local function cGray(text) - return Grichelde.functions.color(Grichelde.COLOR_CODES.GRAY, text) + return Grichelde.F.color(Grichelde.COLOR_CODES.GRAY, text) end local function cDarkgray(text) - return Grichelde.functions.color(Grichelde.COLOR_CODES.DARKGRAY, text) + return Grichelde.F.color(Grichelde.COLOR_CODES.DARKGRAY, text) +end + +local function cGreen(text) + return Grichelde.F.color(Grichelde.COLOR_CODES.GREEN, text) end local function cOrange(text) - return Grichelde.functions.color(Grichelde.COLOR_CODES.ORANGE, text) + return Grichelde.F.color(Grichelde.COLOR_CODES.ORANGE, text) +end + +local function cRed(text) + return Grichelde.F.color(Grichelde.COLOR_CODES.RED, text) end -- faster function lookups by mapping to local refs -Grichelde.functions = {} -Grichelde.functions.IsAddOnLoaded = _G.IsAddOnLoaded -Grichelde.functions.assert = _G.assert -Grichelde.functions.type = _G.type -Grichelde.functions.print = _G.print -Grichelde.functions.nilOrEmpty = nilOrEmpty -Grichelde.functions.pairs = _G.pairs -Grichelde.functions.ipairs = _G.ipairs -Grichelde.functions.spairs = spairs -Grichelde.functions.tContains = _G.tContains -Grichelde.functions.tFilter = tFilter -Grichelde.functions.tInsert = _G.table.insert -Grichelde.functions.tConcat = _G.table.concat -Grichelde.functions.tSize = tSize -Grichelde.functions.tIsEmpty = tIsEmpty -Grichelde.functions.tSort = _G.table.sort -Grichelde.functions.tClone = tClone -Grichelde.functions.tNext = _G.next -Grichelde.functions.tWipe = _G.wipe -Grichelde.functions.setmetatable = _G.setmetatable -Grichelde.functions.getmetatable = _G.getmetatable -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.split = _G.strsplit -Grichelde.functions.toUpper = _G.strupper -Grichelde.functions.toLower = _G.strlower -Grichelde.functions.isChar = isChar -Grichelde.functions.isNumber = isNumber -Grichelde.functions.isUpper = isUpper -Grichelde.functions.isLower = isLower -Grichelde.functions.isCapital = isCapital -Grichelde.functions.color = color -Grichelde.functions.cPrefix = cPrefix -Grichelde.functions.cYellow = cYellow -Grichelde.functions.cGray = cGray -Grichelde.functions.cDarkgray = cDarkgray -Grichelde.functions.cOrange = cOrange -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.lenUtf8 = _G.strlenutf8 -Grichelde.functions.toString = _G.tostring -Grichelde.functions.toNumber = _G.tonumber -Grichelde.functions.max = _G.math.max -Grichelde.functions.min = _G.math.min \ No newline at end of file +Grichelde.F = { + IsAddOnLoaded = _G.IsAddOnLoaded, + assert = _G.assert, + type = _G.type, + print = _G.print, + nilOrEmpty = nilOrEmpty, + pairs = _G.pairs, + ipairs = _G.ipairs, + spairs = spairs, + tContains = _G.tContains, + tFilter = tFilter, + tInsert = _G.table.insert, + tConcat = _G.table.concat, + tSize = tSize, + tIsEmpty = tIsEmpty, + tSort = _G.table.sort, + tClone = tClone, + tNext = _G.next, + tWipe = _G.wipe, + setmetatable = _G.setmetatable, + getmetatable = _G.getmetatable, + select = _G.select, + unpack = _G.unpack, + find = _G.string.find, + sub = _G.string.sub, + gsub = _G.string.gsub, + match = _G.strmatch, + gmatch = _G.string.gmatch, + join = _G.strjoin, + split = _G.strsplit, + toUpper = _G.strupper, + toLower = _G.strlower, + getNextCharUtf8 = getNextCharUtf8, + isLetter = isLetter, + isNumber = isNumber, + isUpper = isUpper, + isLower = isLower, + isCapital = isCapital, + capitalize = capitalize, + color = color, + cPrefix = cPrefix, + cYellow = cYellow, + cGray = cGray, + cDarkgray = cDarkgray, + cGreen = cGreen, + cOrange = cOrange, + cRed = cRed, + toByte = _G.string.byte, + bytes2Char = _G.string.char, + format = _G.string.format, + rep = _G.string.rep, + trim = _G.strtrim, + length = _G.string.len, + lengthUtf8 = _G.strlenutf8, + toString = _G.tostring, + toNumber = _G.tonumber, + max = _G.math.max, + min = _G.math.min, +} \ No newline at end of file diff --git a/GricheldeDatabase.lua b/GricheldeDatabase.lua index a2343cb..9afc8b5 100644 --- a/GricheldeDatabase.lua +++ b/GricheldeDatabase.lua @@ -1,11 +1,11 @@ -- read namespace from global env local _G = _G -local Grichelde = _G.Grichelde +local Grichelde = _G.Grichelde or {} local pairs, tInsert, tClone, unpack, join, toString - = Grichelde.functions.pairs, Grichelde.functions.tInsert, Grichelde.functions.tClone, Grichelde.functions.unpack, Grichelde.functions.join, Grichelde.functions.toString + = Grichelde.F.pairs, Grichelde.F.tInsert, Grichelde.F.tClone, Grichelde.F.unpack, Grichelde.F.join, Grichelde.F.toString -function Grichelde:GetDefaultConfig() +function Grichelde.getDefaultConfig() return { global = {}, profile = { @@ -24,12 +24,12 @@ function Grichelde:GetDefaultConfig() }, replacements = { ["**"] = { - active = true, order = 9999, searchText = "", replaceText = "", exactCase = false, consolidate = true, + matchWhen = 2, stopOnMatch = false, }, replacement_10 = { @@ -42,13 +42,18 @@ function Grichelde:GetDefaultConfig() searchText = "t", replaceText = "ck", }, + replacement_12 = { + order = 12, + searchText = "p", + replaceText = "b", + }, } } } end function Grichelde:LoadDatabase() - local db = LibStub("AceDB-3.0"):New(self.name .."DB", self:GetDefaultConfig(), true) + local db = LibStub("AceDB-3.0"):New(self.name .."DB", self.getDefaultConfig(), true) db.RegisterCallback(self, "OnNewProfile", "RefreshOptions") db.RegisterCallback(self, "OnProfileChanged", "RefreshOptions") @@ -68,8 +73,8 @@ function Grichelde:SyncToDatabase(info, val) local option = self.db.profile local path = 1 while (path < #info) do - if info[path] ~= "mappings" then - option = option[info[path]] -- or nil + if (info[path] ~= "mappings") then + option = option[info[path]] end path = path + 1 end @@ -87,8 +92,8 @@ function Grichelde:ReadFromDatabase(info) local option = self.db.profile local path = 1 while (path <= #info) do - if info[path] ~= "mappings" then - option = option[info[path]] -- or nil + if (info[path] ~= "mappings") then + option = option[info[path]] end path = path + 1 end @@ -120,7 +125,7 @@ function Grichelde:ReorderReplacements() while count < size do local replName = orderToName[index] - if replName and replacements[replName] then + if (replName ~= nil) and (replacements[replName] ~= nil) then self:TracePrint("ReorderReplacements : replName: %s, replTable", replName) self:TracePrint(replacements[replName]) local order = Grichelde.MAPPING_OFFSET + count diff --git a/GricheldeMinimap.lua b/GricheldeMinimap.lua index d2512ba..5af0ec0 100644 --- a/GricheldeMinimap.lua +++ b/GricheldeMinimap.lua @@ -1,19 +1,19 @@ -- read namespace from global env local _G = _G -local Grichelde = _G.Grichelde +local Grichelde = _G.Grichelde or {} --- add Minimap button function Grichelde:MinimapButton() local function clickHandler(_, button) - if button == 'LeftButton' then + if (button == "LeftButton") then self:ToggleOptions() - elseif button == 'RightButton' then + elseif (button == "RightButton") then self:ToggleActivation() end end local function updateTooltip(tooltip) - if not tooltip or not tooltip.AddLine then return end + if (tooltip == nil) or (tooltip.AddLine == nil) then return end local tooltipTitle = self:Format(self.L.Minimap_Tooltip_Enabled, self.L.AddonName) if not self.db.profile.enabled then @@ -35,7 +35,7 @@ function Grichelde:MinimapButton() end local darkened = Grichelde.MINIMAP_ENABLED - if not self.db.profile.enabled then + if (self.db.profile.enabled == false) then darkened = Grichelde.MINIMAP_DARKENDED end @@ -63,7 +63,7 @@ function Grichelde:ToggleMinimapButton() self.db.profile.minimapButton.hide = not self.db.profile.minimapButton.hide self:DebugPrint("ToggleMinimapButton : hidden: ", self.db.profile.minimapButton.hide) - if self.db.profile.minimapButton.hide then + if (self.db.profile.minimapButton.hide == true) then self:HideMinimapButton() else self:ShowMinimapButton() @@ -71,37 +71,54 @@ function Grichelde:ToggleMinimapButton() end function Grichelde:ShowMinimapButton() - if self.icon then + if (self.icon ~= nil) then self.icon:Show(self.name) end end function Grichelde:HideMinimapButton() - if self.icon then + if (self.icon ~= nil) then self.icon:Hide(self.name) end end function Grichelde:ToggleActivation() - self.db.profile.enabled = not self.db.profile.enabled - -- refresh option UI if open at the moment - self.dialog:SelectGroup(self.name, "enabled") + if (self.db.profile.enabled == true) then + self:Deactivate() + else + self:Activate() + end +end - local formatString = self.L.AddonLoaded - local darkened = Grichelde.MINIMAP_ENABLED - if not self.db.profile.enabled then - formatString = self.L.AddonUnloaded - darkened = Grichelde.MINIMAP_DARKENDED +function Grichelde:Activate() + self.db.profile.enabled = true + + -- refresh option UI if open at the moment + if (self.dialog ~= nil) and (self.dialog.OpenFrames[self.name] ~= nil) then + self.dialog:SelectGroup(self.name, "enabled") + local namePlusVersion = self:Format(self.L.AddonNamePlusVersion, self.L.AddonName, self.version) + local statusText = self:Format(self.L.AddonLoaded, namePlusVersion) + self.dialog.OpenFrames[self.name]:SetStatusText(statusText) end - if self.dialog ~= nil and self.dialog.OpenFrames[self.name] ~= nil then + self.ldb.iconR = Grichelde.MINIMAP_ENABLED + self.ldb.iconG = Grichelde.MINIMAP_ENABLED + self.ldb.iconB = Grichelde.MINIMAP_ENABLED +end + +function Grichelde:Deactivate() + self.db.profile.enabled = false + + -- refresh option UI if open at the moment + if (self.dialog ~= nil) and (self.dialog.OpenFrames[self.name] ~= nil) then + self.dialog:SelectGroup(self.name, "enabled") local namePlusVersion = self:Format(self.L.AddonNamePlusVersion, self.L.AddonName, self.version) - local statusText = self:Format(formatString, namePlusVersion) + local statusText = self:Format(self.L.AddonUnloaded, namePlusVersion) self.dialog.OpenFrames[self.name]:SetStatusText(statusText) end - self.ldb.iconR = darkened - self.ldb.iconG = darkened - self.ldb.iconB = darkened + self.ldb.iconR = Grichelde.MINIMAP_DARKENDED + self.ldb.iconG = Grichelde.MINIMAP_DARKENDED + self.ldb.iconB = Grichelde.MINIMAP_DARKENDED end diff --git a/GricheldeOptions.lua b/GricheldeOptions.lua index 2aca601..09bf5ea 100644 --- a/GricheldeOptions.lua +++ b/GricheldeOptions.lua @@ -1,9 +1,9 @@ -- read namespace from global env local _G = _G -local Grichelde = _G.Grichelde +local Grichelde = _G.Grichelde or {} -local nilOrEmpty, pairs, tContains, tWipe, find, match, color, toString, toNumber - = Grichelde.functions.nilOrEmpty, Grichelde.functions.pairs, Grichelde.functions.tContains, Grichelde.functions.tWipe, Grichelde.functions.find, Grichelde.functions.match, Grichelde.functions.color, Grichelde.functions.toString, Grichelde.functions.toNumber +local assert, nilOrEmpty, pairs, ipairs, spairs, tContains, tInsert, tClone, tWipe, find, match, cPrefix, cGray, cGreen, cOrange, cRed, format, toString, toNumber + = Grichelde.F.assert, Grichelde.F.nilOrEmpty, Grichelde.F.pairs, Grichelde.F.ipairs, Grichelde.F.spairs, Grichelde.F.tContains, Grichelde.F.tInsert, Grichelde.F.tClone, Grichelde.F.tWipe, Grichelde.F.find, Grichelde.F.match, Grichelde.F.cPrefix, Grichelde.F.cGray, Grichelde.F.cGreen, Grichelde.F.cOrange, Grichelde.F.cRed, Grichelde.F.format, Grichelde.F.toString, Grichelde.F.toNumber local selectedExample = 1 @@ -206,41 +206,52 @@ function Grichelde:CreateOptionsUI() header = { order = 2, type = "description", - name = self.L.Options_Help_Examples0_Header, + name = self.L.Options_Help_Examples_Header, fontSize = "medium", - width = 2.5, + width = 2.1 }, dropDown = { order = 3, type = "select", name = "", - --width = 1, - values = { - self.L.Options_Help_Examples1_Select, - self.L.Options_Help_Examples2_Select, - self.L.Options_Help_Examples3_Select, - self.L.Options_Help_Examples4_Select, - self.L.Options_Help_Examples5_Select, - self.L.Options_Help_Examples6_Select, --- self.L.Options_Help_Examples7_Select, - }, - set = function(info, val) selectedExample = val end, + width = 1.2, + values = function(_) return self:ExtractExampleNames() end, + set = function(_, val) selectedExample = val end, get = function(_) - self.options.args.help.args.examples.args.header.name = self.L["Options_Help_Examples" .. selectedExample .. "_Header"] - self.options.args.help.args.examples.args.example.name = self.L["Options_Help_Examples" .. selectedExample .. "_Text"] + self.options.args.help.args.examples.args.header.name = self.L.Options_Help_Examples[selectedExample].desc + self.options.args.help.args.examples.args.example.name = self:ExtractExampleCodes(selectedExample), self.dialog:SelectGroup(self.name, "help", "examples", "header.name") return selectedExample end, }, + spacer3 = { + order = 3, + type = "description", + name = "", + desc = "", + width = 2.3, + }, + import = { order = 4, + type = "execute", + width = 1, + confirm = selectedExample > 0, + name = self.L.Options_Help_Examples_Import_Name, + -- desc = self.L.Options_Help_Examples_Import_Desc, + desc = function() return format(self.L.Options_Help_Examples_Import_Desc, cPrefix(self.L.Options_Help_Examples[selectedExample].name)) end, + func = function(info) self:ImportExample(selectedExample) end, + }, + + spacer4 = { + order = 6, type = "header", name = "", }, example = { - order = 5, + order = 7, type = "description", - name = self.L.Options_Help_Examples0_Text, + name = self.L.Options_Help_Examples_Text, fontSize = "medium", }, }, @@ -283,14 +294,14 @@ function Grichelde:CreateMapping(offset) width = 0.15, func = function(info) self:MoveDown(info) end, }, ---[[ - spacer4 = { + spacer5 = { order = 2, type = "description", name = "", - width = 0.1, + desc = "", + width = 0.05, }, -]] +--[[ active = { order = 3, type = "toggle", @@ -298,8 +309,40 @@ function Grichelde:CreateMapping(offset) desc = self.L.Options_Mapping_Enabled_Desc, width = 2.2, }, - delete = { +]] + matchWhenLabel = { + order = 3, + type = "description", + name = self.L.Options_Mapping_MatchWhen_Name, + desc = self.L.Options_Mapping_MatchWhen_Desc, + fontSize = "medium", + width = 0.3, + }, + matchWhen = { order = 4, + type = "select", + name = " ", + desc = self.L.Options_Mapping_MatchWhen_Desc, + values = { + self.L.Options_Mapping_MatchWhen_Select1, + self.L.Options_Mapping_MatchWhen_Select2, + self.L.Options_Mapping_MatchWhen_Select3, + self.L.Options_Mapping_MatchWhen_Select4, + self.L.Options_Mapping_MatchWhen_Select5, + self.L.Options_Mapping_MatchWhen_Select6, + }, + width = 1.4, + }, + spacer6 = { + order = 5, + type = "description", + name = "", + desc = "", + width = 0.1, + }, + + delete = { + order = 6, type = "execute", confirm = true, confirmText = self.L.Options_Mapping_Delete_ConfirmText, @@ -324,21 +367,21 @@ function Grichelde:CreateMapping(offset) }, exactCase = { - order = 20, + order = 21, type = "toggle", name = self.L.Options_Mapping_ExactCase_Name, desc = self.L.Options_Mapping_ExactCase_Desc, width = "full", }, consolidate = { - order = 21, + order = 22, type = "toggle", name = self.L.Options_Mapping_Consolidate_Name, desc = self.L.Options_Mapping_Consolidate_Desc, width = "full", }, stopOnMatch = { - order = 22, + order = 23, type = "toggle", name = self.L.Options_Mapping_StopOnMatch_Name, desc = self.L.Options_Mapping_StopOnMatch_Desc, @@ -350,7 +393,7 @@ end function Grichelde:SetupOptions() -- add DB-backed profiles to UI options - local options = self:CreateOptionsUI() + local options = self:CreateOptionsUI(self) options.args.profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db) options.args.profiles.order = 8 options.args.profiles.disabled = false @@ -363,19 +406,18 @@ function Grichelde:SetupOptions() return options, dialog end -function Grichelde:RefreshOptions(event) +function Grichelde:RefreshOptions(event, _, profileName) self:DebugPrint("RefreshOptions : event:", event) - local currentProfile = color(Grichelde.COLOR_CODES.GREEN, self.db:GetCurrentProfile()) - if event == "OnNewProfile" then - self:PrefixedPrint(self.L.Profiles_Created, currentProfile) - elseif event == "OnProfileChanged" then - self:PrefixedPrint(self.L.Profiles_Loaded, currentProfile) - elseif event == "OnProfileDeleted" then - self:PrefixedPrint(self.L.Profiles_Deleted, currentProfile) - elseif event == "OnProfileCopied" then - self:PrefixedPrint(self.L.Profiles_Copied, currentProfile) - elseif event == "OnProfileReset" then - self:PrefixedPrint(self.L.Profiles_Reset, currentProfile) + if (event == "OnNewProfile") then + self:PrefixedPrint(self.L.Profiles_Created, cGreen(self.db:GetCurrentProfile())) + elseif (event == "OnProfileChanged") then + self:PrefixedPrint(self.L.Profiles_Loaded, cGreen(self.db:GetCurrentProfile())) + elseif (event == "OnProfileDeleted") then + self:PrefixedPrint(self.L.Profiles_Deleted, cRed(profileName)) + elseif (event == "OnProfileCopied") then + self:PrefixedPrint(self.L.Profiles_Copied, cOrange(profileName)) + elseif (event == "OnProfileReset") then + self:PrefixedPrint(self.L.Profiles_Reset, cOrange(self.db:GetCurrentProfile())) else self:DebugPrint("Refreshing Profile %s on options change: %s", self.db:GetCurrentProfile(), event) end @@ -386,7 +428,7 @@ end function Grichelde:ToggleOptions() self:DebugPrint("ToggleOptions : options open: ", not not self.dialog.OpenFrames[self.name]) - if self.dialog ~= nil and self.dialog.OpenFrames[self.name] ~= nil then + if (self.dialog ~= nil) and (self.dialog.OpenFrames[self.name] ~= nil) then self:CloseOptions() else self:OpenOptions() @@ -394,11 +436,11 @@ function Grichelde:ToggleOptions() end function Grichelde:OpenOptions() - if self.dialog ~= nil then + if (self.dialog ~= nil) then self.dialog:Open(self.name) local formatString = self.L.AddonLoaded - if not self.db.profile.enabled then + if (self.db.profile.enabled == false) then formatString = self.L.AddonUnloaded end @@ -409,7 +451,7 @@ function Grichelde:OpenOptions() end function Grichelde:CloseOptions() - if self.dialog ~= nil then + if (self.dialog ~= nil) then self.dialog:Close(self.name) end end @@ -417,7 +459,7 @@ end --- If all replacements were disabled -- @return (boolean) function Grichelde:IsDisabled(info) - if info and info.option.type == "group" then + if (info and (info.option.type == "group")) then return false end return not self.db.profile.enabled @@ -428,13 +470,13 @@ end function Grichelde:IsMappingActive(info) self:TracePrint("IsMappingActive : info") for i = 0, #info do - self:TracePrint("%d = %s", i, info[i]) + self:TracePrint("IsMappingActive : info[%d] = %s", i, info[i]) end - if info and info.option.type == "group" then + if (info and (info.option.type == "group")) then return true end - if not self.db.profile.enabled then + if (self.db.profile.enabled == false) then return false end @@ -445,10 +487,10 @@ function Grichelde:IsMappingActive(info) self:DebugPrint("IsMappingActive : \"%s\"", currentName) self:DebugPrint(replacements[currentName]) - if (tContains({"moveUp", "moveDown", "active", "delete"}, uiElem)) then + if (tContains({"moveUp", "moveDown", "matchWhen", "delete"}, uiElem)) then return true else - return not not replacements[currentName].active + return replacements[currentName].matchWhen > 1 end end @@ -457,20 +499,82 @@ function Grichelde:MappingName(info) -- self:TracePrint(info) local option = self.db.profile.replacements[info[2]] - if nilOrEmpty(option.searchText) and nilOrEmpty(option.replaceText) then - return color(Grichelde.COLOR_CODES.GRAY, self.L.Options_Mapping_EmptyMapping) + if (nilOrEmpty(option.searchText) and nilOrEmpty(option.replaceText)) then + return cGray(self.L.Options_Mapping_EmptyMapping) else local name = self:Format(self.L.Options_Mapping_Group_Name, option.searchText or "", option.replaceText or "") - if option.active == true then + if (option.matchWhen > 1) then return name else - return color(Grichelde.COLOR_CODES.GRAY, name) + return cGray(name) + end + end +end + +function Grichelde:ExtractExampleNames() + local exampleNames = {} + for _, example in ipairs(self.L.Options_Help_Examples) do + tInsert(exampleNames, example.name) + end + return exampleNames +end + +function Grichelde:ExtractExampleCodes(num) + self:TracePrint("ExtractExampleCodes : number is: %d", num) + if (self.L.Options_Help_Examples[num] == nil) or (#self.L.Options_Help_Examples < num) then + self:DebugPrint("ExtractExampleCodes : invalid number: %d", num) + return self.L.Options_Help_Examples_Text + end + + local exampleCodes = "" + for replName, replTable in spairs(self.L.Options_Help_Examples[num].replacements) do + self:TracePrint("ExtractExampleCodes : replacement: %s", replName) + self:TracePrint(replTable) + + if (replTable ~= nil) and (replTable.searchText ~= nil) then + if (exampleCodes ~= "") then + exampleCodes = exampleCodes .. "|n|n" + end + exampleCodes = exampleCodes .. cPrefix(format("%s => %s", replTable.searchText, replTable.replaceText)) end end + return exampleCodes end +function Grichelde:ImportExample(num) + self:TracePrint("ImportExample : number is: %d", num) + if (self.L.Options_Help_Examples[num] == nil) or (#self.L.Options_Help_Examples < num) then + self:DebugPrint("ImportExample : invalid number: %d", num) + end + + local profileName = self.L.Options_Help_Examples[num].name + self:DebugPrint("ImportExample : profile name: %s", profileName) + local allProfiles = self.db:GetProfiles() + + if (not tContains(allProfiles, profileName)) then + -- create new profile if not exists + self.db:SetProfile(profileName) + assert(self.db:GetCurrentProfile() == profileName, "profile was not loaded") + + local exampleProfile = self.db.profile + tWipe(exampleProfile.replacements) + + for replName, replTable in spairs(self.L.Options_Help_Examples[num].replacements) do + self:TracePrint("ImportExample : replacement: %s", replName) + self:TracePrint(replTable) + + if (replName ~= nil) and (replTable ~= nil) and (replTable.searchText ~= nil) then + exampleProfile.replacements[replName] = tClone(replTable) + end + end ---- Create UI options for rhe given replacement table (from DB). + self:RefreshReplacements(self.db.profile.replacements) + else + self:ErrorPrint(self.L.Profiles_AlreadyExistsError, profileName) + end +end + +--- Create UI options for the given replacement table (from DB). --- Usually called with with self.db.profile.replacements -- @param replacementsTable function Grichelde:RefreshReplacements(replacementsTable) @@ -480,7 +584,7 @@ function Grichelde:RefreshReplacements(replacementsTable) -- remove all previous replacements from options (not DB), except header and buttons local replacements = self.options.args.replacements.args or {} for k, _ in pairs(replacements) do - if k and find(k, "^replacement_") then + if (k and (find(k, "^replacement_") ~= nil)) then replacements[k] = nil end end @@ -505,7 +609,7 @@ function Grichelde:AddEmptyMapping() local maxRepl = Grichelde.MAPPING_OFFSET for replName, _ in pairs(replacements) do local num = match(replName, "^replacement_(%d+)") - if num and maxRepl < toNumber(num) then + if (num ~= nil) and (maxRepl < toNumber(num)) then maxRepl = toNumber(num) end end @@ -538,7 +642,7 @@ function Grichelde:MoveUp(info) local currentOrder = toNumber(replNumber) -- if not on top - if currentOrder ~= Grichelde.MAPPING_OFFSET then + if (currentOrder ~= Grichelde.MAPPING_OFFSET) then local swapName = "replacement_" .. toString(currentOrder - 1) -- swap ordering @@ -581,7 +685,7 @@ function Grichelde:MoveDown(info) -- if not last element self:DebugPrint("MoveDown : maxRepl: %d", maxRepl) - if currentOrder < maxRepl then + if (currentOrder < maxRepl) then local swapName = "replacement_" .. toString(currentOrder + 1) -- swap ordering @@ -611,7 +715,7 @@ function Grichelde:GetMoveUpImage(info) local _, replNumber = self:SplitOnFirstMatch(currentName, "_") local currentOrder = toNumber(replNumber) - if (self:IsMappingActive(info) and currentOrder > Grichelde.MAPPING_OFFSET ) then + if (self:IsMappingActive(info) and (currentOrder > Grichelde.MAPPING_OFFSET)) then return Grichelde.ICONS.MOVE_UP else return Grichelde.ICONS.MOVE_UP_DISABLED @@ -634,12 +738,12 @@ function Grichelde:GetMoveDownImage(info) local replacements = self.db.profile.replacements or {} for replName, _ in pairs(replacements) do local num = match(replName, "^replacement_(%d+)") - if num and maxRepl < toNumber(num) then + if (num ~= nil) and (maxRepl < toNumber(num)) then maxRepl = toNumber(num) end end - if (self:IsMappingActive(info) and currentOrder < maxRepl) then + if (self:IsMappingActive(info) and (currentOrder < maxRepl)) then return Grichelde.ICONS.MOVE_DOWN else return Grichelde.ICONS.MOVE_DOWN_DISABLED diff --git a/GricheldeTest.lua b/GricheldeTest.lua new file mode 100644 index 0000000..f922b66 --- /dev/null +++ b/GricheldeTest.lua @@ -0,0 +1,720 @@ +-- import addon read namespace from global env +local _G = _G +local Grichelde = _G.Grichelde or {} + +local find, pairs, tSize, cRed, cGreen, format = Grichelde.F.find, Grichelde.F.pairs, Grichelde.F.tSize, Grichelde.F.cRed, Grichelde.F.cGreen, Grichelde.F.format + +function Grichelde:TestMatch(text, pattern) + -- disable debug print out for testing + local oldLogLevel = Grichelde.logLevel + Grichelde.logLevel = 0 + + local pos1, pos2, cap1, cap2, cap3, cap4, cap5, cap6, cap7, cap8, cap9 = find(text, pattern) + self:PrefixedPrint("TestMatch : text: %s, pattern: %s, pos1: %d, pos2: %d", text, pattern, pos1, pos2) + self:PrefixedPrint("TestMatch : cap1: %s, cap2: %s, cap3: %s, cap4: %s, cap5: %s, cap6: %s, cap7: %s, cap8: %s, cap9: %s", cap1, cap2, cap3, cap4, cap5, cap6, cap7, cap8, cap9) + + -- restore old loglevel + Grichelde.logLevel = oldLogLevel +end + +function Grichelde:RunTests() + local function test(name, replacements, testData) + local i, ok, size = 0, 0, tSize(testData) + for input, expected in pairs(testData) do + local actual = self:ReplaceText(input, replacements, false) + i = i + 1 + if (actual == expected) then + ok = ok + 1 + self:PrefixedPrint("Test \"%s\" (%d/%d) %s: \"%s\" => \"%s\"", name, i, size, cGreen("passed"), input, expected) + else + self:PrefixedPrint("Test \"%s\" (%d/%d) %s: \"%s\" => \"%s\", but was \"%s\"", name, i, size, cRed("failed"), input, expected, actual) + end + end + return ok, size + end + + -- disable debug print out for testing + local oldLogLevel = Grichelde.logLevel + Grichelde.logLevel = 0 + + local ok, all, o, a = 0, 0, 0, 0 + + -- basic tests + o, a = test( + "fehlender Unterkiefer", + { + replacement_10 = { + order = 10, + searchText = "s", + replaceText = "ch", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + }, + { + ["abc"] = "abc", + ["soo"] = "choo", + ["oos"] = "ooch", + ["oso"] = "ocho", + ["sos"] = "choch", + ["ssoo"] = "choo", + ["osso"] = "ocho", + ["ooss"] = "ooch", + ["ABC"] = "ABC", + ["Soo"] = "Choo", + ["ooS"] = "ooCH", + ["oSo"] = "oCho", + ["SOS"] = "CHOCH", + ["SSoo"] = "CHoo", + ["OSSO"] = "OCHO", + ["ooSS"] = "ooCH", + ["schmeissen"] = "chmeichen", + ["Schön"] = "Chön", + } + ) + ok = ok + o + all = all + a + + -- case sensivity and extended replacements + o, a = test( + "Zark", + { + replacement_10 = { + order = 10, + searchText = "Zark", + replaceText = "Schami", + exactCase = false, + consolidate = false, + matchWhen = 2, + stopOnMatch = false, + }, + }, + { + ["Zark"] = "Schami", + ["ZARK"] = "SCHAMI", + ["Zarkilein"] = "Schamiilein", + ["ZARKILEIN"] = "SCHAMIILEIN", + ["Zark!"] = "Schami!", + ["ZARK!"] = "SCHAMI!", + ["Zark ist tot"] = "Schami ist tot", + ["ZARK ist tot"] = "SCHAMI ist tot", + ["Zark ist der Tod"] = "Schami ist der Tod", + ["ZARK IST DER TOD"] = "SCHAMI IST DER TOD", + } + ) + ok = ok + o + all = all + a + + -- start/end of sentence/words + o, a = test( + "wann", + { + replacement_10 = { + order = 10, + searchText = "bcd", + replaceText = "efg", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "uio", + replaceText = "bnm", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "hij", + replaceText = "klm", + exactCase = false, + consolidate = true, + matchWhen = 4, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "nop", + replaceText = "qrs", + exactCase = false, + consolidate = true, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_14 = { + order = 14, + searchText = "tuv", + replaceText = "wxy", + exactCase = false, + consolidate = true, + matchWhen = 6, + stopOnMatch = false, + }, + replacement_15 = { + order = 15, + searchText = "wer", + replaceText = "sdf", + exactCase = false, + consolidate = true, + matchWhen = 7, + stopOnMatch = false, + }, + }, + { + -- replacement_10 + ["bcd"] = "efg", + ["abcdz"] = "aefgz", + ["abcd"] = "aefg", + ["bcdz"] = "efgz", + -- replacement_10 + ["uio"] = "bnm", + ["auioz"] = "auioz", + ["auio"] = "auio", + ["uioz"] = "uioz", + -- replacement_12 + ["hij"] = "klm", + ["ahijz"] = "ahijz", + ["ahij"] = "ahij", + ["hijz"] = "klmz", + -- replacement_13 + ["nop"] = "qrs", + ["anopz"] = "anopz", + ["anop"] = "aqrs", + ["nopz"] = "nopz", + -- replacement_14 + ["tuv"] = "wxy", + ["atuvz"] = "atuvz", + ["atuv"] = "awxy", + ["tuvz"] = "wxyz", + -- replacement_15 + ["wer"] = "wer", + ["awerz"] = "asdfz", + ["awer"] = "awer", + ["werz"] = "werz", + } + ) + ok = ok + o + all = all + a + + o, a = test( + "consolidate", + { + replacement_10 = { + order = 10, + searchText = "a", + replaceText = "b", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "b", + replaceText = "c", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "d", + replaceText = "e", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + }, + { + ["aaa"] = "c", + ["abb"] = "c", + ["abc"] = "c", + ["bbc"] = "c", + ["ace"] = "ce", + ["abe"] = "ce", + ["bbe"] = "ce", + ["ece"] = "ece", + ["ede"] = "ee", + } + ) + ok = ok + o + all = all + a + + -- stop on match + o, a = test( + "stopOnMatch", + { + replacement_10 = { + order = 10, + searchText = "a", + replaceText = "b", + exactCase = false, + consolidate = false, + matchWhen = 2, + stopOnMatch = true, + }, + replacement_11 = { + order = 11, + searchText = "b", + replaceText = "c", + exactCase = false, + consolidate = false, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "c", + replaceText = "d", + exactCase = false, + consolidate = false, + matchWhen = 2, + stopOnMatch = true, + }, + }, + { + ["aaa"] = "bbb", + ["abc"] = "bbc", + ["bbc"] = "ddd", + ["bca"] = "bcb", + } + ) + ok = ok + o + all = all + a + + o, a = test( + "Jar Jar Binks (DE)", + { + replacement_10 = { + order = 10, + searchText = "ver", + replaceText = "va", + exactCase = false, + consolidate = false, + matchWhen = 4, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "en", + replaceText = "'n", + exactCase = false, + consolidate = false, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "er", + replaceText = "a", + exactCase = false, + consolidate = false, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "(%w?)ich", + replaceText = "%1ichse", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_14 = { + order = 14, + searchText = "(d?m?)ir", + replaceText = "%1ichse", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_15 = { + order = 15, + searchText = "du", + replaceText = "du da", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_16 = { + order = 16, + searchText = "er", + replaceText = "erse", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_17 = { + order = 17, + searchText = "sie", + replaceText = "sie da", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_18 = { + order = 18, + searchText = "wir", + replaceText = "wirse", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_19 = { + order = 19, + searchText = "ihr", + replaceText = "ihrse", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_20 = { + order = 20, + searchText = "nicht", + replaceText = "nich", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_21 = { + order = 21, + searchText = "die", + replaceText = "de", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + }, + { + ["Ich kann dich verstehen."] = "Ichse kann dichse vasteh'n.", + ["Wir haben sie die ganze Zeit über nicht verstanden"] = "Wirse hab'n sie da de ganze Zeit üba nich vastand'n", + } + ) + ok = ok + o + all = all + a + + o, a = test( + "Stottern", + { + replacement_10 = { + order = 10, + searchText = "^([^aeiouy]*)([aeiouy])", + replaceText = "%1%2-%1%2-%1%2", + exactCase = false, + consolidate = true, + matchWhen = 4, + stopOnMatch = false, + }, + }, + { + ["Ich mag dich."] = "I-I-Ich mag dich.", + ["Dich mag ich."] = "Di-Di-Dich mag ich.", + } + ) + ok = ok + o + all = all + a + + o, a = test( + "trollifier", + { + replacement_10 = { + order = 10, + searchText = "(%w)(%p?)$", + replaceText = "%1, mon%2", + exactCase = false, + consolidate = false, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "th", + replaceText = "d", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "what are you", + replaceText = "whatcha", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "your?s?", + replaceText = "ya", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_14 = { + order = 14, + searchText = "going to", + replaceText = "gonna", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_15 = { + order = 15, + searchText = "want to", + replaceText = "wanna", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_16 = { + order = 16, + searchText = "ing", + replaceText = "in'", + exactCase = false, + consolidate = true, + matchWhen = 5, + stopOnMatch = false, + }, + }, + { + ["What are you going to do when they come for you?"] = "Whatcha gonna do when dey come for ya, mon?", + ["That's what young people are doing"] = "Dat's what young people are doin', mon", + } + ) + ok = ok + o + all = all + a + + o, a = test( + "Jar Jar Binks (EN)", + { + replacement_10 = { + order = 10, + searchText = "me", + replaceText = "mesa", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "I am", + replaceText = "Mesa", + exactCase = true, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "I'm", + replaceText = "Mesa", + exactCase = true, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "I", + replaceText = "Me", + exactCase = true, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_14 = { + order = 14, + searchText = "you are", + replaceText = "yousa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_15 = { + order = 15, + searchText = "you're", + replaceText = "yousa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_16 = { + order = 16, + searchText = "your", + replaceText = "yous", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_17 = { + order = 17, + searchText = "(s?)he is", + replaceText = "%1hesa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_18 = { + order = 18, + searchText = "(s?)he's", + replaceText = "%1hesa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_19 = { + order = 19, + searchText = "they", + replaceText = "daysa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_20 = { + order = 20, + searchText = "them", + replaceText = "them-sa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_21 = { + order = 21, + searchText = "ing", + replaceText = "in'", + exactCase = false, + consolidate = true, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_22 = { + order = 22, + searchText = "the", + replaceText = "da", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_23 = { + order = 23, + searchText = "th", + replaceText = "d", + exactCase = false, + consolidate = false, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_24 = { + order = 24, + searchText = "yes", + replaceText = "yesa", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_25 = { + order = 25, + searchText = "oka?y?", + replaceText = "okeeday", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + }, + { + ["I hear your voice through the thrilling grapewine."] = "Me hear yous voice drough da drillin' grapewine.", + ["They gave them their OK"] = "Daysa gave dem-sa deir OKEEDAY", + } + ) + ok = ok + o + all = all + a + + o, a = test( + "old-fashioned", + { + replacement_10 = { + order = 10, + searchText = "oi", + replaceText = "oy", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "([^aeiou]*)([aeiou])", + replaceText = "%1%2e", + exactCase = false, + consolidate = true, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "yours", + replaceText = "thy", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "youe", + replaceText = "tho", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + }, + { + ["Do you want to kill yours?"] = "Doe thou want toe kill thy?", + } + ) + ok = ok + o + all = all + a + + if (ok == all) then + self:PrefixedPrint("All %d tests %s", all, cGreen("passed")) + else + self:PrefixedPrint("%d test %s, %d tests %s", all - ok, cRed("failed"), ok, cGreen("passed")) + end + + -- restore old loglevel + Grichelde.logLevel = oldLogLevel +end \ No newline at end of file diff --git a/GricheldeUpgrade.lua b/GricheldeUpgrade.lua index 04cdbd0..8b918f4 100644 --- a/GricheldeUpgrade.lua +++ b/GricheldeUpgrade.lua @@ -1,8 +1,9 @@ -- read namespace from global env local _G = _G -local Grichelde = _G.Grichelde +local Grichelde = _G.Grichelde or {} -local pairs, find, color, cOrange, toNumber = Grichelde.functions.pairs, Grichelde.functions.find, Grichelde.functions.color, Grichelde.functions.cOrange, Grichelde.functions.toNumber +local pairs, find, cGreen, cOrange, cRed, toNumber + = Grichelde.F.pairs, Grichelde.F.find, Grichelde.F.cGreen, Grichelde.F.cOrange, Grichelde.F.cRed, Grichelde.F.toNumber function Grichelde:Upgrade_To_v060() self:PrefixedPrint(self.L.Upgrade_ToVersion, cOrange("0.6.0")) @@ -67,45 +68,89 @@ function Grichelde:Upgrade_To_v080() return 0, 8, 0 end +function Grichelde:Upgrade_To_v090() + self:PrefixedPrint(self.L.Upgrade_ToVersion, cOrange("0.9.0")) + + local replacements = self.db.profile.replacements or {} + self:DebugPrint("Upgrade_To_v090 : old replacements") + self:DebugPrint(replacements) + + for _, replTable in pairs(replacements) do + if (replTable["active"] == true) then + replTable["matchWhen"] = 2 + else + replTable["matchWhen"] = 1 + end + replTable["active"] = nil + end + + self:DebugPrint("Upgrade_To_v090 : new replacements") + self:DebugPrint(self.db.profile) + return 0, 9, 0 +end + function Grichelde:UpgradeDatabase() local dbVersion = self.db.global.version or "0.0.0" self:DebugPrint("Database version:", dbVersion) - local _, _, maj, min, pat = find(dbVersion, "(%d+)%.(%d+)%.(%d+).*") - local major, minor, patch = toNumber(maj) or 0, toNumber(min) or 0, toNumber(pat) or 0 + local dbMajor, dbMinor, dbPatch = self:ParseVersion(dbVersion) + local gMajor, gMinor, gPatch = self:ParseVersion(self.version) + + local downGrade = false + if (dbMajor > gMajor) then + downGrade = true + elseif dbMajor == gMajor then + if (dbMinor > gMinor) then + downGrade = true + elseif dbMinor == gMinor then + if (dbPatch > gPatch) then + downGrade = true + end + end + end - local upgrade = 0 - local error = false + if downGrade then + self:PrefixedPrint(cRed(self.L.Downgrade_Detected), self.L.AddonName) + else + local upgrade = 0 + local error = false - if major == 0 then - if minor < 6 then - upgrade = upgrade + 1 - major, minor, patch = self:Upgrade_To_v060(dbVersion) - end - if minor < 7 then - upgrade = upgrade + 1 - major, minor, patch = self:Upgrade_To_v070(dbVersion) - end - if minor == 7 then - if patch < 2 then + if (dbMajor == 0) then + if (dbMinor < 6) then upgrade = upgrade + 1 - major, minor, patch = self:Upgrade_To_v072(dbVersion) + dbMajor, dbMinor, dbPatch = self:Upgrade_To_v060(dbVersion) + end + if (dbMinor < 7) then + upgrade = upgrade + 1 + dbMajor, dbMinor, dbPatch = self:Upgrade_To_v070(dbVersion) + end + if (dbMinor == 7) then + if (dbPatch < 2) then + upgrade = upgrade + 1 + dbMajor, dbMinor, dbPatch = self:Upgrade_To_v072(dbVersion) + end + end + if (dbMinor < 8) then + upgrade = upgrade + 1 + dbMajor, dbMinor, dbPatch = self:Upgrade_To_v080(dbVersion) + end + if (dbMinor < 9) then + upgrade = upgrade + 1 + dbMajor, dbMinor, dbPatch = self:Upgrade_To_v090(dbVersion) end end - if minor < 8 then - upgrade = upgrade + 1 - major, minor, patch = self:Upgrade_To_v080(dbVersion) + + if (upgrade == 0) or (error == false) then + -- bump version number even if no update is required + self.db.global.version = self:Format("%d.%d.%d", gMajor, gMinor, gPatch) end - end - if upgrade == 0 then - self:DebugPrint("Database up-to-date") - -- bump version number even if no update is required - self.db.global.version = self.version - else - if not error then - self.db.global.version = self.version - self:PrefixedPrint(color(Grichelde.COLOR_CODES.GREEN, self.L.Upgrade_Successful)) + if (upgrade == 0) then + self:DebugPrint("Database up-to-date") + elseif (error == false) then + self:PrefixedPrint(cGreen(self.L.Upgrade_Successful)) + else + self:PrefixedPrint(cRed(self.L.Upgrade_Error)) end end end \ No newline at end of file diff --git a/GricheldeUtils.lua b/GricheldeUtils.lua index 20c6fcf..4c001fd 100644 --- a/GricheldeUtils.lua +++ b/GricheldeUtils.lua @@ -1,18 +1,33 @@ -- import addon read namespace from global env local _G = _G -local Grichelde = _G.Grichelde +local Grichelde = _G.Grichelde or {} -local type, print, pairs, tSize, select, unpack, find, color, cGray, cDarkgray, cPrefix, format, rep, toString - = Grichelde.functions.type, Grichelde.functions.print, Grichelde.functions.pairs, Grichelde.functions.tSize, Grichelde.functions.select, Grichelde.functions.unpack, Grichelde.functions.find, Grichelde.functions.color, Grichelde.functions.cGray, Grichelde.functions.cDarkgray, Grichelde.functions.cPrefix, Grichelde.functions.format, Grichelde.functions.rep, Grichelde.functions.toString +local type, print, pairs, tSize, select, unpack, find, sub, gsub, cGray, cDarkgray, cRed, cPrefix, format, rep, toString, toNumber + = Grichelde.F.type, Grichelde.F.print, Grichelde.F.pairs, Grichelde.F.tSize, Grichelde.F.select, Grichelde.F.unpack, Grichelde.F.find, Grichelde.F.sub, Grichelde.F.gsub, Grichelde.F.cGray, Grichelde.F.cDarkgray, Grichelde.F.cRed, Grichelde.F.cPrefix, Grichelde.F.format, Grichelde.F.rep, Grichelde.F.toString, Grichelde.F.toNumber + +function Grichelde:ParseVersion(version) + local _, _, major, minor, patch, ext = find(version, "(%d+)%.(%d+)%.(%d+)(.*)") + local preBuild, build = ext, "" + if (sub(ext, 1, 1) == "-") then + local b = find(ext, "+", 2) + if (b ~= nil) then + preBuild = sub(ext, 1, b) + build = sub(ext, b + 1) + else + preBuild = sub(ext, 1, b) + end + end + return toNumber(major) or 0, toNumber(minor) or 0, toNumber(patch) or 0, preBuild, build +end -- show strings differently to distinguish them from numbers -local function plainValue(val) - if val == nil then +function Grichelde:plainValue(val) + if (val == nil) then return "" - elseif type(val) == "string" then + elseif (type(val) == "string") then return '"' .. val .. '"' - elseif type(val) == "table" then - if tSize(val) > 0 then + elseif (type(val) == "table") then + if (tSize(val) > 0) then return toString(val) else return "{}" @@ -23,33 +38,36 @@ local function plainValue(val) end --- Prints any value to default channel, do NOT return a string. -local function tPrint(val, indent, known, printFunc) +function Grichelde:tPrint(val, indent, known, printFunc) local printF = printFunc or print indent = indent or 0 known = known or {} - if val == nil then + if (val == nil) then printF(rep(" ", indent) .. "") - elseif type(val) == "string" then + elseif (type(val) == "string") then printF(rep(" ", indent) .. "\"" .. val .. "\"") - elseif type(val) == "table" then - if tSize(val) > 0 then + elseif (type(val) == "table") then + if (tSize(val) > 0) then for key, value in pairs(val) do - if value == nil then - printF(rep(" ", indent) .. plainValue(key) .. " = ") - elseif type(value) == "table" then - printF(rep(" ", indent) .. plainValue(key) .. " = {") - if tSize(value) > 0 then + if (value == nil) then + printF(rep(" ", indent) .. self:plainValue(key) .. " = ") + elseif (type(value) == "table") then + printF(rep(" ", indent) .. self:plainValue(key) .. " = {") + if (tSize(value) > 0) then if not known[value] then - tPrint(value, indent + 4, known, printF) + self:tPrint(value, indent + 4, known, printF) known[value] = true else - printF(" " .. plainValue(value)) + printF(" " .. self:plainValue(value)) end end printF(rep(" ", indent) .. "}") else - printF(rep(" ", indent) .. plainValue(key) .. " = " .. plainValue(value)) + local k = self:plainValue(key) + local v = self:plainValue(value) + --print( "k: " .. k .. ", v: " ..v) + printF(rep(" ", indent) .. k .. " = " .. v) end end else @@ -62,7 +80,7 @@ end --- Splits at first word of a text line function Grichelde:SplitOnFirstMatch(text, delimPattern, start) - if text == nil then return nil end + if (text == nil) then return nil end local pattern = "^(.-)" .. (delimPattern or " " ) .."(.*)" local pos = start or 1 self:TracePrint("SplitOnFirstMatch : text: %s, pattern: %s, start: %d", text, pattern, start) @@ -88,82 +106,85 @@ function Grichelde:TestMatch(text, pattern) end function Grichelde:Format(message, ...) - if ( not message ) then + if (message == nil) then return "" - elseif type(message) == "string" then - if ( not find(message, "%%")) then + elseif (type(message) == "string") then + if (find(message, "%%") == nil) then + --print("message: ", message) + --print("...: ", ...) return message, ... else local l = select("#", ...) - if l > 0 then + if (l > 0) then -- sanitize nil values in vararg local packed = { ... } for i = 1, l do packed[i] = toString(packed[i]) or "nil" + --print("packed[i] = ", packed[i]) + --packed[i] = gsub(packed[i], "%%(%d)", "%%%%%1") end -- print("packed = ", packed) -- self:tPrint(packed) -- cannot assign unpacked to a vararg variable and print it for debug -- Manually set count as unpack() stops on nil (bug with #table) return format(message, unpack(packed, 1, l)) + else + return message end end end end ---- deprecated -function Grichelde:Print(...) - print(self:Format(...)) -end - function Grichelde:PrefixedPrint(...) print(cPrefix(self.L.AddonName) .. ":", self:Format(...)) end function Grichelde:ErrorPrint(...) - print(cPrefix(self.L.AddonName) .. ": " .. color(self.COLOR_CODES.RED, self:Format(...))) + print(cPrefix(self.L.AddonName) .. ": " .. cRed(self:Format(...))) end function Grichelde:DebugPrint(obj, ...) - self:LogPrint(Grichelde.LOG_LEVEL.DEBUG, function(...) - print(cGray(self.L.AddonName) .. ":", self:Format(...)) - end, obj, ...) + if (self.logLevel >= Grichelde.LOG_LEVEL.DEBUG) then + self:LogPrint(function(...) + print(cGray(self.L.AddonName) .. ":", self:Format(...)) + end, obj, ...) + end end function Grichelde:TracePrint(obj, ...) - self:LogPrint(Grichelde.LOG_LEVEL.TRACE, function(...) - print(cDarkgray(self.L.AddonName) .. ":", self:Format(...)) - end, obj, ...) + if (self.logLevel >= Grichelde.LOG_LEVEL.TRACE) then + self:LogPrint(function(...) + print(cDarkgray(self.L.AddonName) .. ":", self:Format(...)) + end, obj, ...) + end end -function Grichelde:LogPrint(logLevel, printFunc, obj, ...) - if (self.logLevel >= logLevel) then - local printF = printFunc or print - if obj == nil then - printF("") - else - if type(obj) == "string" then - local l = select("#", ...) - if ( l == 0 or not find(obj, "%%")) then - printF(obj, ...) - else - -- 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 - local fmtMsg = format(obj, unpack(packed, 1, l)) -- manually set count as unpack() stops on nil (bug with #table) - printF(fmtMsg) - end - elseif type(obj) == "table" then - tPrint(obj, 0, {}, printF) +function Grichelde:LogPrint(printFunc, obj, ...) + local printF = printFunc or print + if obj == nil then + printF("") + else + if type(obj) == "string" then + local l = select("#", ...) + if (l == 0) or (find(obj, "%%") == nil) then + printF(obj, ...) else - printF(plainValue(obj)) + -- 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 + local fmtMsg = format(obj, unpack(packed, 1, l)) -- manually set count as unpack() stops on nil (bug with #table) + printF(fmtMsg) end + elseif (type(obj) == "table") then + self:tPrint(obj, 0, {}, printF) + else + printF(self:plainValue(obj)) end end end @@ -196,7 +217,7 @@ end function Grichelde:ToogleMappings() local AceGUI = LibStub("AceGUI-3.0") - if self.debugFrame then + if (self.debugFrame ~= nil) then AceGUI:Release(self.debugFrame) self.debugFrame = nil else @@ -231,7 +252,7 @@ function Grichelde:ToogleMappings() local configBox = AceGUI:Create("MultiLineEditBox"); configBox:SetLabel("") local text = "" - tPrint(replacements, 0, {}, function(s) text = text .. s .. "|n" end) + self:tPrint(replacements, 0, {}, function(s) text = text .. s .. "|n" end) configBox:SetText(text) configBox:SetFullWidth(true) --configBox:SetFullHeight(true) diff --git a/README.md b/README.md index d5d32d0..817217f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Grichelde - Text replacer -Grichelde is a WoW Classic Addon that replaces any characters or words you typed in a chatbox with any replacement text or word you specified. -You can define any search and replace text arbitrarily. The replacement is done **before** the text is send to others/in the target channel. -It does **not** change txt others have written in the chat/channel. +Grichelde is a WoW Classic Addon that replaces any characters or words you typed in a chatbox according to your replacement rules and text. +The replacement is done **before** the text is send to others/in the target channel. It does **not** change text others have written in the chat/channel, +but the text they will see **from you** when you sent it. Its purpose it to simulate a speaking disability of your toon and hereby strengthen immersion in roleplay. Initially started as a helper addon for a roleplaying friend, Grichelde can be used for much more, like @@ -12,6 +12,9 @@ Initially started as a helper addon for a roleplaying friend, Grichelde can be u * write out abbreviations for you * create hilarious moments during roleplay +However it does not replace slash commands with other slash commands, it only works for chat text. It you want to replace commands, +please look at more sophisticated chat addons like [Prat](https://www.curseforge.com/wow/addons/prat-3-0). + ## Disclaimer #### No Warranty The addon is provided "AS IS" and comes without warranty of any kind of function or correctness (for more details, consult the GPL 3 license). @@ -35,19 +38,19 @@ Only slash commands, item links, textures, placeholders and ooc-markers are excl #### My replacement is not taken. -After entering a search or replacement text, you see a button "Okay" next to yout input. This is **not** a validation message, +After entering a search or replacement text, you see a button "Okay" next to your input. This is **not** a validation message, but the save button for text. This is a restriction from the UI library and can be seen in other addons as well. -Please click on "Okay" button to save the input permanently. +Please click on "Okay" to save the input permanently. If it still does not work or gives you errors, please read the next question. -#### I have to disable the Addon frequently. Is there a more elegant solution +#### I have to disable the Addon frequently. Is there a more elegant solution? Actually there are two solutions: 1. A right-click on the minimap button will disable Grichelde instantly. Right-click a second time will activate it again. Easy, isn't it? -2. You can disable Grichelde permanently and forcefully replace the sentence in the chatbox. I call it "Grichelde-On-Demand" :) +2. You can disable Grichelde permanently and forcefully replace a sentence in the chatbox with manual override. I call it "Grichelde-On-Demand" :) In the chatbox put `/gri` or `/grichelde` in front of your typed text, you can also include the target channel, - i.e. `/gri /guild hello there` and Grichelde will apply the active replacements even if guild channel or Grichelde was disabled. + i.e. `/gri /guild hello there` and Grichelde will apply the active replacements and rules even if guild channel or Grichelde was disabled. #### I get errors, what should I do? @@ -56,13 +59,15 @@ You can bring up a small windows with your mapping by entering the `/gri mapping #### Why that strange name? -Grichelde is the name of an Undead rogue without a jaw, who was played in RP sessions with a guild member. -She started replacing "s" and "t" letters manually for each line in the chat, which became cumbersome over time. +Grichelde is the name of an Undead rogue without a jaw, who was played in RP sessions by a guild member. +She started replacing `s` and `t` letters manually for each word in the chat, which became cumbersome over time. (If you ever wondered how an Undead without a jaw sounds like, its really hilarious, you should try it.) Without spelling errors, "Griselde" in German is an old-fashioned female first name. #### I'm a Pro. Does it support regular expressions? -This is actually an unofficial feature. In general the searchText is passed in as Lua, so yes regex can be used in lookups. -There are two caveats: first, Lua does not support PCRE syntax, as it would bloat Lua's simplicity and performance (read [here](http://www.lua.org/pil/20.1.html) why). -Secondly, Grichelde does not support capture groups in the replacement text, so matches get lost. \ No newline at end of file +RegEx are very powerful search and replacement patterns commonly used in programming. Technically all searches the addon performs on the input are done +with regular expression methods, however Lua unfortunately does not support full [PCRE](https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions) syntax and is very limited (read [here](http://www.lua.org/pil/20.1.html) why). +Nevertheless some patterns can be used like the anchors start of line `^` or end of line `$`, capturing groups `(hello) (world)`, character classes like numbers `%d` or (inversed) sets `[^%p]`. + +Capture groups can be accessed in the replacement text with `%` like in `%2 %1`. Also there are some mappings using RegEx in the Example section. \ No newline at end of file diff --git a/localisation/deDE.lua b/localisation/deDE.lua index 85bc832..28bed52 100644 --- a/localisation/deDE.lua +++ b/localisation/deDE.lua @@ -6,9 +6,9 @@ local Grichelde = _G.Grichelde local L = LibStub('AceLocale-3.0'):NewLocale(AddonName, 'deDE') if not L then return end -local cYellow = Grichelde.functions.cYellow -local cGray = Grichelde.functions.cGray -local cPrefix = Grichelde.functions.cPrefix +local cYellow = Grichelde.F.cYellow +local cGray = Grichelde.F.cGray +local cPrefix = Grichelde.F.cPrefix -- system messages L.AddonName = "Grichelde" @@ -17,6 +17,8 @@ L.AddonLoaded = "%s unterst\195\188tzt Euch jetzt bei euren Sprachschwierigkeite L.AddonUnloaded = "%s wartet geduldig Euch weiter unterst\195\188tzen zu d\195\188rfen." L.Upgrade_ToVersion = "Hebe Databank auf Version %s an." L.Upgrade_Successful = "Upgrade erfolgreich." +L.Upgrade_Error = "Upgrade fehlgeschlagen!" +L.Downgrade_Detected = "Downgrade erkannt, %s kann sich m\195\182glichweise fehlerhaft verhalten!" -- debug L.Debug_Options = "Optionen" @@ -41,6 +43,7 @@ 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!" +L.Profiles_AlreadyExistsError = "Das Profil %s existiert bereits!" -- minimap L.Minimap_Tooltip_Enabled = "%s" @@ -100,8 +103,19 @@ L.Options_Replacements_Header = "Die Vorkommen links vom Pfeil \"=>\" werden in L.Options_Mapping_Group_Name = "%s => %s" L.Options_Mapping_Group_Desc = "Dieses Vorkommen wird in den aktivierten Kan\195\164len ersetzt." L.Options_Mapping_EmptyMapping = "(keine)" -L.Options_Mapping_Enabled_Name = "Aktiv" -L.Options_Mapping_Enabled_Desc = "Diese Ersetzung wird durchgef\195\188hrt" +L.Options_Mapping_MoveUp_Name = "^" +L.Options_Mapping_MoveUp_Desc = "nach oben verschieben" +L.Options_Mapping_MoveDown_Name = "v" +L.Options_Mapping_MoveDown_Desc = "nach unten verschieben" +L.Options_Mapping_MatchWhen_Name = "wann:" +L.Options_Mapping_MatchWhen_Desc = "F\195\188hrt die Ersetzung nur durch, wenn der Suchtext |nirgendwo vorkommt (), |nwenn der Suchtext \195\188bereinstimmt, |n, |noder , |noder aber nicht dazwischen, |noder nur in der Wortmitte aber ." +L.Options_Mapping_MatchWhen_Select1 = "nie (deaktivert)" +L.Options_Mapping_MatchWhen_Select2 = "immer" +L.Options_Mapping_MatchWhen_Select3 = "als ganzes Wort" +L.Options_Mapping_MatchWhen_Select4 = "nur am Anfang eines Worts" +L.Options_Mapping_MatchWhen_Select5 = "nur am Ende eines Worts" +L.Options_Mapping_MatchWhen_Select6 = "nur am Anfang oder Ende eines Worts" +L.Options_Mapping_MatchWhen_Select7 = "nie am Anfang und Ende eines Worts" L.Options_Mapping_SearchText_Name = "Suchtext:" L.Options_Mapping_SearchText_Desc = "Dieser Text wird in der Chateingabe gesucht." L.Options_Mapping_ReplaceText_Name = "Ersetzung:" @@ -112,10 +126,6 @@ L.Options_Mapping_Consolidate_Name = "Zusammenfassen aufeinanderfolgender Treffe L.Options_Mapping_Consolidate_Desc = "Wenn durch die Ersetzung die Zeichenfolge mehrfach hintereinander steht,|nfasse sie zu einem Vorkommen zusammen." L.Options_Mapping_StopOnMatch_Name = "Anhalten nach Treffer" L.Options_Mapping_StopOnMatch_Desc = "F\195\188hrt keine nachfolgenden Ersetzungen mehr durch, wenn dieser Eintrag ein Suchtreffer war." -L.Options_Mapping_MoveUp_Name = "^" -L.Options_Mapping_MoveUp_Desc = "nach oben verschieben" -L.Options_Mapping_MoveDown_Name = "v" -L.Options_Mapping_MoveDown_Desc = "nach unten verschieben" L.Options_Mapping_Delete_Name = "L\195\182schen" L.Options_Mapping_Delete_Desc = "L\195\182scht diese Zuweisung." L.Options_Mapping_Delete_ConfirmText="Diese Zuweisung l\195\182schen?" @@ -145,9 +155,10 @@ L.Options_Help_Basics = cYellow("Reihenfolge") .. "|nDas Zusammenfassen aufeinanderfolgender Treffer vermeidet unsch\195\182ne Wiederholungen, die durch die Ersetzung entstehen k\195\182nnen. " .. "Die Zusammenfassung wird erst nach der Ersetzung vorgenommen, d.h. am vollst\195\164ndig ersetzten Text f\195\188r jede Zuordnung. " .. "|nMit der Zuordnung " .. cPrefix("\"s\" => \"sch\"") .. " wird aus " .. cPrefix("\"Tasse\" => \"Tasche\"") .. " statt " .. cPrefix("\"Taschsche\"") .. ", " - .. "aber " .. cPrefix("\"schmeissen\" => \"schchmeischen\"") .. ". Solche Randbedingungen beseitigt in der Regel eine weitere Zuordnung wie " .. cPrefix("\"chch\" => \"ch\"") .. "." + .. "aber " .. cPrefix("\"schmeissen\" => \"schchmeichen\"") .. ". Solche Randbedingungen beseitigt in der Regel eine weitere Zuordnung wie " .. cPrefix("\"schch\" => \"sch\"") .. "." .. "|n|n" .. cYellow("Anhalten nach Treffer") - .. "|nEs werden keine weiteren Ersetzungen mehr vorgenommen, wenn die aktuelle Zuordnung zutreffend ist. Alle nachfolgenden Zuordnungen werden dann \195\188bersprungen. Wenn kein Treffer vorliegt, werden die restlichen Zuordnung ganz normal weiter abgearbeitet." + .. "|nEs werden keine weiteren Ersetzungen mehr vorgenommen, wenn die aktuelle Zuordnung zutreffend ist. Alle nachfolgenden Zuordnungen werden dann \195\188bersprungen. " + .. "Wenn kein Treffer vorliegt, werden die restlichen Zuordnung ganz normal weiter abgearbeitet." L.Options_Help_Expert = cYellow("verk\195\188rzende/verl\195\163ngernde Ersetzungen") .. "|nIst der Ersetzungstext k\195\188rzer als der eigentliche Suchtext, werden die \195\188bersch\195\188\195\159igen Zeichen des Suchtreffers entfernt. " .. "Ist der Ersetzungstext l\195\163nger, werden die \195\188brigen Zeichen nach dem Treffer hinten drangehangen. Dabei wird die Gro\195\159- und Kleinschreibung des letzten Zeichens ber\195\188cksichtigt, " @@ -157,39 +168,397 @@ L.Options_Help_Expert = cYellow("verk\195\188rzende/verl\195\163ngernde Ersetzun .. "|n|n" .. cYellow("Standby") .. "|nErsetzungen k\195\182nnen auch nur bei Bedarf durchgef\195\188hrt werden, selbst wenn das Addon oder ein Kanal deaktivert wurde. " .. "Vor der Eingabe in der Chatbox schreibt man " .. cPrefix("/gri").. " oder " .. cPrefix("/grichelde").. " und optional noch den Zielkanal " - .. "z.B. " .. cPrefix("\"/gri /p hallo da dr\195\188ben\"") .. " und alle aktiven Zuordnungen werden ersetzt, selbst wenn der Gruppenkanal oder das Addon nicht aktiv sind." + .. "z.B. " .. cPrefix("\"/gri /g hallo leute\"") .. " und alle aktiven Zuordnungen werden ersetzt, selbst wenn der Gildenkanal oder das Addon nicht aktiv sind." .. "|n|n" .. cYellow("Regul\195\164re Ausdr\195\188cke") - .. "|nRegEx sind sehr m\195\163chtige Such- und Ersetzunsgmuster die h\195\163ufig in der Programmierung verwendet werden. Technisch gesehen benutzt das Addon f\195\188r die Suchen des Eingabetextes bereits regul\195\163ren Ausdr\195\188cke. " - .. "Das Eingeben von RegEx als Suchtext ist allerings eine inoffizielle Funktion und hat zwei gro\195\159e Einschr\195\163nkungen: " - .. "|n1. Leider unterst\195\188tzt Lua nicht den vollst\195\163ndigen Umfang von PCRE. Trotzdem k\195\182nnen einige Muster verwendet werden wie Zeilenanfang " .. cPrefix("\"^\"") .. " " - .."oder Zeilenende " .. cPrefix("\"$\"") .. ", Zeichenklassen wie Zahlen " .. cPrefix("\"%d\"") .. " oder (negierte) Auswahlen " .. cPrefix("\"[^%p]\"") .. ". " - .. "|n2. Es werden keine Gruppen im Ersetzungstest unterst\195\188tzt, so da\195\159 Gruppen einfach verloren gehen. Wegen der Gro\195\159- und Kleinschreibung und steigender Komplexit\195\163t ist diese Funktion auch f\195\188r die Zukunft nicht geplant. " + .. "|nRegEx sind sehr m\195\163chtige Such- und Ersetzunsgmuster die h\195\163ufig in der Programmierung verwendet werden. Generell werden RegEx in den Suchtexten \195\188bernommen, " + .. "aber Lua unterst\195\188tzt nicht den vollst\195\163ndigen Umfang von PCRE. Trotzdem funktionieren viele Muster wie Anker bei Zeilenanfang " .. cPrefix("\"^\"") .. " oder Zeilenende " .. cPrefix("\"$\"") + .. ", Gruppen " .. cPrefix("\"(Hallo) (Welt)\"") .. "Zeichenklassen wie Zahlen " .. cPrefix("\"%d\"") .. " oder (negierte) Auswahlen " .. cPrefix("\"[^%p]\"") .. ". " + .. "Auf Gruppen kann im Ersetzungtext mit % zugegriffen werden" .. cPrefix("\"%2 %1\"") .. "." .. "|nIm Beispiel-Reiter gibt es einige Ersetzungen, welche mit regul\195\164ren Ausdr\195\188cke umgesetzt wurden." L.Options_Help_Examples_Note = cYellow("Hinweis:") .. " Dieses Addon bef\195\188rwortet nicht und beabsichtig nicht Personen mit (Fremd-)Sprachproblemen |nzu verletztem oder herabzuw\195\188rdigen. Die Verantwortung f\195\188r den Einsatz des Addons obliegt dem Benutzer. |nBitte verwendet die Funktion respektvoll und zur\195\188ckhaltend gegen\195\188ber anderen Mitspielern." -L.Options_Help_Examples0_Header = cYellow("Beispiel") -L.Options_Help_Examples0_Text = "Bitte ein Beispiel aus der Auswahlbox ausw\195\164hlen." -L.Options_Help_Examples1_Select = "fehlender Unterkiefer" -L.Options_Help_Examples1_Header = cYellow("S und P werden durch Zisch- und Klacklaute ersetzt.") -L.Options_Help_Examples1_Text = cPrefix("s => ch") .. "|n|n" .. cPrefix("t => ck") -L.Options_Help_Examples2_Select = "Trollifizierung" -L.Options_Help_Examples2_Header = cYellow("L\195\164\195\159t euch fast wie ein echter Troll klingen.") -L.Options_Help_Examples2_Text = cPrefix("%.$ => , maan.") .. "|n|n" .. cPrefix("ir => ia") .. "|n|n" .. cPrefix("ch => ck") .. "|n|n" .. cPrefix("g => ch") .. "|n|n" .. cPrefix("qu => kw") .. "|n|n" .. cPrefix("t => d") -L.Options_Help_Examples3_Select = "Altmodisch" -L.Options_Help_Examples3_Header = cYellow("Benutzt eine antiquiertere Schreibweise.") -L.Options_Help_Examples3_Text = cPrefix("ei => ey") .. "|n|n" .. cPrefix("\195\159 => sz") -L.Options_Help_Examples4_Select = "Abk\195\188rzungen" -L.Options_Help_Examples4_Header = cYellow("Viel sagen, wenig tippen.") -L.Options_Help_Examples4_Text = cPrefix("gz => Herzlichen Gl\195\188ckwunsch") .. "|n|n" .. cPrefix("gn8 => Gute Nacht") .. "|n|n" .. cPrefix("afk => Bin mal eben weg (AFK)") .. "|n|n" .. cPrefix("MC => Geschmolzener Kern") -L.Options_Help_Examples5_Select = "Eigen-, Kose- und Ortsnamen" -L.Options_Help_Examples5_Header = cYellow("Ersetzt Spielernamen, NPCs oder Orte durch andere Ausdr\195\188cke.") -L.Options_Help_Examples5_Text = "Exakte Gro\195\159- und Kleinschreibung wird empfohlen|n|n" .. cPrefix("Sylvanas => die rachs\195\188chtige Bansheek\195\182nigin") .. "|n|n" .. cPrefix("R\195\188tzkn\195\188bbel => R\195\188tzi") .. "|n|n" .. cPrefix("Unterstadt => Undercity") -L.Options_Help_Examples6_Select = "Lispeln" -L.Options_Help_Examples6_Header = cYellow("Aussprache von S und Z wird zu einem Zischlaut") -L.Options_Help_Examples6_Text = cPrefix("sch => ch") .. "|n|n" .. cPrefix("s => fs") .. "|n|n" .. cPrefix("z => ts") .. "|n|n" .. cPrefix("chs => x") -L.Options_Help_Examples7_Select = "Stottern" -L.Options_Help_Examples7_Header = cYellow("Stottern") -L.Options_Help_Examples7_Text = "p[%s]-$" +L.Options_Help_Examples_Header = cYellow("Beispiel") +L.Options_Help_Examples_Text = "Bitte ein Beispiel aus der Auswahlbox ausw\195\164hlen." +L.Options_Help_Examples_Import_Name = "Importieren" +L.Options_Help_Examples_Import_Desc = "Importiert das ausgew\195\164hlte Beispiel in ein neues Profil %s." +L.Options_Help_Examples_Import_ConfirmText = "Wird das Beispiel %s in das neue Profil %s importieren." + +L.Options_Help_Examples = { + { + name = "fehlender Unterkiefer", + desc = cYellow("S und P werden durch Zisch- und Klacklaute ersetzt."), + replacements = { + replacement_10 = { + order = 10, + searchText = "s", + replaceText = "ch", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "t", + replaceText = "ck", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "p", + replaceText = "b", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + } + }, { + name = "Trollifizierung", + desc = cYellow("L\195\164\195\159t euch fast wie Vol'jin klingen."), + replacements = { + replacement_10 = { + order = 10, + searchText = "(%w)(%p?)$", + replaceText = "%1, maan%2", + exactCase = false, + consolidate = false, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "ir(r?)", + replaceText = "ia", + exactCase = false, + consolidate = false, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "ch", + replaceText = "ck", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "g", + replaceText = "ch", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_14 = { + order = 14, + searchText = "qu", + replaceText = "kw", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_15 = { + order = 15, + searchText = "t", + replaceText = "d", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_16 = { + order = 16, + searchText = "er", + replaceText = "a", + exactCase = false, + consolidate = false, + matchWhen = 5, + stopOnMatch = false, + }, + } + }, { + name = "Jar Jar Binks", + desc = cYellow("L\195\164\195\159t euch sprechen wie ein ungeschickter Gungan"), + replacements = { + replacement_10 = { + order = 10, + searchText = "ver", + replaceText = "va", + exactCase = false, + consolidate = false, + matchWhen = 4, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "en", + replaceText = "'n", + exactCase = false, + consolidate = false, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "er", + replaceText = "a", + exactCase = false, + consolidate = false, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "(%w?)ich", + replaceText = "%1ichse", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_14 = { + order = 14, + searchText = "(d?m?)ir", + replaceText = "%1ichse", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_15 = { + order = 15, + searchText = "du", + replaceText = "du da", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_16 = { + order = 16, + searchText = "er", + replaceText = "erse", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_17 = { + order = 17, + searchText = "sie", + replaceText = "sie da", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_18 = { + order = 18, + searchText = "wir", + replaceText = "wirse", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_19= { + order = 19, + searchText = "ihr", + replaceText = "ihrse", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_20 = { + order = 20, + searchText = "nicht", + replaceText = "nich", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_21 = { + order = 21, + searchText = "die", + replaceText = "de", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + } + }, { + name = "Altmodisch", + desc = cYellow("Benutzt eine antiquiertere Schreibweise."), + replacements = { + replacement_10 = { + order = 10, + searchText = "ei", + replaceText = "ey", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "eu", + replaceText = "oy", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "\195\159", + replaceText = "sz", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + } + }, { + name = "Abk\195\188rzungen", + desc = cYellow("Viel sagen, wenig tippen."), + replacements = { + replacement_10 = { + order = 10, + searchText = "gz", + replaceText = "Herzlichen Gl\195\188ckwunsch", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "gn8", + replaceText = "Gute Nacht", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "afk", + replaceText = "Bin mal kurz weg. (AFK)", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "MC", + replaceText = "Geschmolzener Kern", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + } + }, { + name = "Eigen-, Kose- und Ortsnamen", + desc = cYellow("Ersetzt Spielernamen, NPCs oder Orte durch andere Ausdr\195\188cke."), + replacements = { + replacement_10 = { + order = 10, + searchText = "Sylvanas", + replaceText = "die rachs\195\188chtige Bansheek\195\182nigin", + exactCase = true, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "R\195\188tzkn\195\188bbel", + replaceText = "R\195\188tzi", + exactCase = true, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "Unterstadt", + replaceText = "Undercity", + exactCase = true, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + } + }, { + name = "Lispeln", + desc = cYellow("Aussprache von S und Z wird zu einem Zischlaut"), + replacements = { + replacement_10 = { + order = 10, + searchText = "sch", + replaceText = "ch", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "s", + replaceText = "fs", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "z", + replaceText = "ts", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "chs", + replaceText = "x", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + } + }, { + name = "Stottern", + desc = cYellow("Wiederholt Vokale am Satzanfang"), + replacements = { + replacement_10 = { + order = 10, + searchText = "^([^aeiouy]*)([aeiouy])", + replaceText = "%1%2-%1%2-%1%2", + exactCase = false, + consolidate = true, + matchWhen = 4, + stopOnMatch = false, + }, + } + }, +} L.IgnorePattern_Star = "Stern" L.IgnorePattern_Circle = "Kreis" diff --git a/localisation/enUS.lua b/localisation/enUS.lua index 589d474..8f96f9a 100644 --- a/localisation/enUS.lua +++ b/localisation/enUS.lua @@ -6,9 +6,9 @@ local Grichelde = _G.Grichelde local L = LibStub('AceLocale-3.0'):NewLocale(AddonName, 'enUS', true) if not L then return end -local cYellow = Grichelde.functions.cYellow -local cGray = Grichelde.functions.cGray -local cPrefix = Grichelde.functions.cPrefix +local cYellow = Grichelde.F.cYellow +local cGray = Grichelde.F.cGray +local cPrefix = Grichelde.F.cPrefix -- system messages L.AddonName = "Grichelde" @@ -17,6 +17,8 @@ L.AddonLoaded = "%s happily assists you with your spelling disabilities now." L.AddonUnloaded = "%s patiently waits to support you again when needed." L.Upgrade_ToVersion = "Upgrade database to version %s." L.Upgrade_Successful = "Upgrade successful." +L.Upgrade_Error = "Upgrade failed!" +L.Downgrade_Detected = "Downgrade detected, %s might not work correctly!" -- debug L.Debug_Options = "Options" @@ -41,6 +43,7 @@ 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!" +L.Profiles_AlreadyExistsError = "The profile %s already exists!" -- minimap L.Minimap_Tooltip_Enabled = "%s" @@ -100,8 +103,19 @@ L.Options_Replacements_Header = "All matches on the lefthand side of the arrow \ L.Options_Mapping_Group_Name = "%s => %s" L.Options_Mapping_Group_Desc = "This lookup will be replaced in activated channels." L.Options_Mapping_EmptyMapping = "(none)" -L.Options_Mapping_Enabled_Name = "active" -L.Options_Mapping_Enabled_Desc = "This replacement will be processed." +L.Options_Mapping_MoveUp_Name = "^" +L.Options_Mapping_MoveUp_Desc = "move up" +L.Options_Mapping_MoveDown_Name = "v" +L.Options_Mapping_MoveDown_Desc = "move down" +L.Options_Mapping_MatchWhen_Name = "when:" +L.Options_Mapping_MatchWhen_Desc = "Replacement is only done if the search text matches either |nanywhere (), |nif the search text mantches , |nolny at the , |nor at the , |nor but not in between, |nor only in the middle of each word, but ." +L.Options_Mapping_MatchWhen_Select1 = "never (disabled)" +L.Options_Mapping_MatchWhen_Select2 = "always" +L.Options_Mapping_MatchWhen_Select3 = "as a whole word" +L.Options_Mapping_MatchWhen_Select4 = "start of each word" +L.Options_Mapping_MatchWhen_Select5 = "end of each word" +L.Options_Mapping_MatchWhen_Select6 = "only at start and end of each word" +L.Options_Mapping_MatchWhen_Select7 = "never at start or end of any word" L.Options_Mapping_SearchText_Name = "Search for:" L.Options_Mapping_SearchText_Desc = "This text is looked up in your chat input box." L.Options_Mapping_ReplaceText_Name = "Replacement:" @@ -112,10 +126,6 @@ L.Options_Mapping_Consolidate_Name = "consolidate consecutive matches" L.Options_Mapping_Consolidate_Desc = "If after the replacement a text sequence is repeated|ndirectly after another, treat them as one occurrence." L.Options_Mapping_StopOnMatch_Name = "stop on match" L.Options_Mapping_StopOnMatch_Desc = "Stops looking for any following replacements, when this one matched." -L.Options_Mapping_MoveUp_Name = "^" -L.Options_Mapping_MoveUp_Desc = "move up" -L.Options_Mapping_MoveDown_Name = "v" -L.Options_Mapping_MoveDown_Desc = "move down" L.Options_Mapping_Delete_Name = "Delete" L.Options_Mapping_Delete_Desc = "Deletes this replacement mapping." L.Options_Mapping_Delete_ConfirmText = "Delete this replacement mapping?" @@ -158,38 +168,440 @@ L.Options_Help_Expert = cYellow("shortening/lengthening replacements") .. "In the chatbox put " .. cPrefix("/gri") .. " or " .. cPrefix("/grichelde") .. " in front of your typed text, you can also include the target channel, " .. "i.e. " .. cPrefix("\"/gri /guild hello there\"") .. " and the active replacements are applied even if the guild channel or global switch was disabled." .. "|n|n" .. cYellow("Regular Expressions") - .. "|nRegex are very powerful search and replacement patterns commonly used in programming. Technically all searches the addon performs on the input text are done with regular expression methods. " - .. "Entering regex as search text however is an unofficial feature and has two major caveats: " - .. "|n1. Unfornately Lua does not support full PCRE syntax and is very limited. Nethertheless some patterns can be used like start of line " .. cPrefix("\"^\"") .. " or end of line " .. cPrefix("\"$\"") .. ", " - .. "character classes like numbers " .. cPrefix("\"%d\"") .. " or (inversed) sets " .. cPrefix("\"[^%p]\"") .. ". " - .. "|n2. There is no support for capture groups in the replacement text, so matches get lost. Because of case sensivity and complexity there are no plans to support this." - .. "|nAnyway, there are some mappings using RegEx in the Example secion." - + .. "|nRegEx are very powerful search and replacement patterns commonly used in programming. Technically all searches the addon performs on the input are done with regular expression methods, " + .. "however Lua unfortunately does not support full PCRE syntax and is very limited. Nevertheless some patterns can be used like the anchors start of line " .. cPrefix("\"^\"") .. " or end of line " .. cPrefix("\"$\"") + .. ", capturing groups " .. cPrefix("\"(hello) (world)\"") .. "character classes like numbers " .. cPrefix("\"%d\"") .. " or (inversed) sets " .. cPrefix("\"[^%p]\"") .. ". " + .. "Capture groups can be accessed in the replacement text with % like in " .. cPrefix("\"%2 %1\"").. "." + .. "|nAlso there are some mappings using RegEx in the Example section." L.Options_Help_Examples_Note = cYellow("Note:") .. " This addon does not encourange or intend to hurt or to tease people with speaking disabilities or language disorders. The responsibility rest on the user completely. Please use the features with care and respect to other players." -L.Options_Help_Examples0_Header = cYellow("Example") -L.Options_Help_Examples0_Text = "Select an example from the dropdown above." -L.Options_Help_Examples1_Select = "absent jaw" -L.Options_Help_Examples1_Header = cYellow("S and P will be replaced by sibilant and clack sounds.") -L.Options_Help_Examples1_Text = cPrefix("s => ch") .. "|n|n" .. cPrefix("t => ck") -L.Options_Help_Examples2_Select = "trollifier" -L.Options_Help_Examples2_Header = cYellow("Almost sound like a real Troll.") -L.Options_Help_Examples2_Text = cPrefix("%.$ => , mon.") .. "|n|n" .. cPrefix("th => d") .. "|n|n" .. cPrefix("you => ya") .. "|n|n" .. cPrefix("ing => in'") -L.Options_Help_Examples3_Select = "old-fashioned" -L.Options_Help_Examples3_Header = cYellow("Use an outdate pronounciation.") -L.Options_Help_Examples3_Text = cPrefix("oi => oy") .. "|n|n" .. cPrefix("do => doe") .. "|n|n" .. cPrefix("go => goe") .. "|n|n" .. cPrefix("you => thou") .. "|n|n" .. cPrefix("yours => thy") .. "|n|n" .. cPrefix("be => bee") .. "|n|n" .. cPrefix("we => wee") -L.Options_Help_Examples4_Select = "abbreviations" -L.Options_Help_Examples4_Header = cYellow("Say much, type less.") -L.Options_Help_Examples4_Text = cPrefix("gz => Congratulations") .. "|n|n" .. cPrefix("gn8 => Good night") .. "|n|n" .. cPrefix("afk => I'm temporarikly not available (AFK)") .. "|n|n" .. cPrefix("MC => Molten Core") -L.Options_Help_Examples5_Select = "Proper names" -L.Options_Help_Examples5_Header = cYellow("Replace player names, NPCs or locations.") -L.Options_Help_Examples5_Text = "exact case is recommended|n|n" .. cPrefix("Sylvanas => the revengeful banshee queen") .. "|n|n" .. cPrefix("Asmongold => Asmon") .. "|n|n" .. cPrefix("Crossroads => X-roads") -L.Options_Help_Examples6_Select = "lisp" -L.Options_Help_Examples6_Header = cYellow("S and Z will become a sibilant") -L.Options_Help_Examples6_Text = cPrefix("s => th") .. "|n|n" .. cPrefix("ch => tsh") .. "|n|n" .. cPrefix("z => tsh") .. "|n|n" .. cPrefix("dg => ck") -L.Options_Help_Examples7_Select = "stammer" -L.Options_Help_Examples7_Header = cYellow("stammer") -L.Options_Help_Examples7_Text = "p[% s]-$" +L.Options_Help_Examples_Header = cYellow("Example") +L.Options_Help_Examples_Text = "Select an example from the dropdown above." +L.Options_Help_Examples_Import_Name = "Import" +L.Options_Help_Examples_Import_Desc = "Imports the selected example into a new profile." +L.Options_Help_Examples_Import_ConfirmText = "This will import the example %s into the nre profile %s." + +L.Options_Help_Examples = { + { + name = "absent jaw", + desc = cYellow("S and P will be replaced by sibilant and clack sounds."), + replacements = { + replacement_10 = { + order = 10, + searchText = "s", + replaceText = "ch", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "t", + replaceText = "ck", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "p", + replaceText = "b", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + } + }, { + name = "trollifier", + desc = cYellow("Almost sound like Vol'jin."), + replacements = { + replacement_10 = { + order = 10, + searchText = "(%w)(%p?)$", + replaceText = "%1, mon%2", + exactCase = false, + consolidate = false, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "th", + replaceText = "d", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "what are you", + replaceText = "whatcha", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "your?s?", + replaceText = "ya", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_14 = { + order = 14, + searchText = "going to", + replaceText = "gonna", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_15 = { + order = 15, + searchText = "want to", + replaceText = "wanna", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_16 = { + order = 16, + searchText = "ing", + replaceText = "in'", + exactCase = false, + consolidate = true, + matchWhen = 5, + stopOnMatch = false, + }, + } + }, { + name = "Jar Jar Binks", + desc = cYellow("Lets you sound like a clumsy Gungan"), + replacements = { + replacement_10 = { + order = 10, + searchText = "me", + replaceText = "mesa", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "I am", + replaceText = "Mesa", + exactCase = true, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "I'm", + replaceText = "Mesa", + exactCase = true, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "I", + replaceText = "Me", + exactCase = true, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_14 = { + order = 14, + searchText = "you are", + replaceText = "yousa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_15 = { + order = 15, + searchText = "you're", + replaceText = "yousa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_16 = { + order = 16, + searchText = "your", + replaceText = "yous", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_17 = { + order = 17, + searchText = "(s?)he is", + replaceText = "%1hesa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_18 = { + order = 18, + searchText = "(s?)he's", + replaceText = "%1hesa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_19 = { + order = 19, + searchText = "they", + replaceText = "daysa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_20 = { + order = 20, + searchText = "them", + replaceText = "them-sa", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_21 = { + order = 21, + searchText = "ing", + replaceText = "in'", + exactCase = false, + consolidate = true, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_22 = { + order = 22, + searchText = "the", + replaceText = "da", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_23 = { + order = 23, + searchText = "th", + replaceText = "d", + exactCase = false, + consolidate = false, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_24 = { + order = 24, + searchText = "yes", + replaceText = "yesa", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_25 = { + order = 25, + searchText = "oka?y?", + replaceText = "okeeday", + exactCase = false, + consolidate = false, + matchWhen = 3, + stopOnMatch = false, + }, + } + }, { + name = "old-fashioned", + desc = cYellow("Use an outdated pronounciation."), + replacements = { + replacement_10 = { + order = 10, + searchText = "oi", + replaceText = "oy", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "([^aeiou]*)([aeiou])", + replaceText = "%1%2e", + exactCase = false, + consolidate = true, + matchWhen = 5, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "yours", + replaceText = "thy", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "youe", + replaceText = "thou", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + } + }, { + name = "abbreviations", + desc = cYellow("Say much, type less."), + replacements = { + replacement_10 = { + order = 10, + searchText = "gz", + replaceText = "Congratulations", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "gn8", + replaceText = "Good night", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "afk", + replaceText = "I'm temporarily unavailable (AFK)", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "MC", + replaceText = "Molten Core", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + } + }, { + name = "Proper names", + desc = cYellow("Replace player names, NPCs or locations."), + replacements = { + replacement_10 = { + order = 10, + searchText = "Sylvanas", + replaceText = "the revengeful banshee queen", + exactCase = true, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "Asmon", + replaceText = "Asmongold", + exactCase = true, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "x%-?roads", + replaceText = "Crossroads", + exactCase = false, + consolidate = true, + matchWhen = 3, + stopOnMatch = false, + }, + } + }, { + name = "Lisp", + desc = cYellow("S and Z will become sibilant"), + replacements = { + replacement_10 = { + order = 10, + searchText = "s", + replaceText = "th", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_11 = { + order = 11, + searchText = "ch", + replaceText = "tsh", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_12 = { + order = 12, + searchText = "z", + replaceText = "tsh", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + replacement_13 = { + order = 13, + searchText = "dg", + replaceText = "ck", + exactCase = false, + consolidate = true, + matchWhen = 2, + stopOnMatch = false, + }, + } + }, { + name = "stammer", + desc = cYellow("Repeats vowels at the beginning of a sentence"), + replacements = { + replacement_10 = { + order = 10, + searchText = "^([^aeiouy]*)([aeiouy])", + replaceText = "%1%2-%1%2-%1%2", + exactCase = false, + consolidate = true, + matchWhen = 4, + stopOnMatch = false, + }, + } + }, +} L.IgnorePattern_Star = "Star" L.IgnorePattern_Circle = "Circle" diff --git a/twitch/channels-de.jpg b/twitch/channels-de.jpg new file mode 100644 index 0000000..2adbb4b Binary files /dev/null and b/twitch/channels-de.jpg differ diff --git a/twitch/channels-de.png b/twitch/channels-de.png deleted file mode 100644 index 9f116be..0000000 Binary files a/twitch/channels-de.png and /dev/null differ diff --git a/twitch/channels-en.jpg b/twitch/channels-en.jpg new file mode 100644 index 0000000..c01d6d1 Binary files /dev/null and b/twitch/channels-en.jpg differ diff --git a/twitch/channels-en.png b/twitch/channels-en.png deleted file mode 100644 index 44a9da0..0000000 Binary files a/twitch/channels-en.png and /dev/null differ diff --git a/twitch/example-en.png b/twitch/example-en.png index d097973..2908eef 100644 Binary files a/twitch/example-en.png and b/twitch/example-en.png differ diff --git a/twitch/example-import-de.jpg b/twitch/example-import-de.jpg new file mode 100644 index 0000000..c385da1 Binary files /dev/null and b/twitch/example-import-de.jpg differ diff --git a/twitch/example-import-en.jpg b/twitch/example-import-en.jpg new file mode 100644 index 0000000..921340c Binary files /dev/null and b/twitch/example-import-en.jpg differ diff --git a/twitch/example-list-de.jpg b/twitch/example-list-de.jpg new file mode 100644 index 0000000..98c575b Binary files /dev/null and b/twitch/example-list-de.jpg differ diff --git a/twitch/example-list-en.jpg b/twitch/example-list-en.jpg new file mode 100644 index 0000000..afa5476 Binary files /dev/null and b/twitch/example-list-en.jpg differ diff --git a/twitch/examples-de.png b/twitch/examples-de.png deleted file mode 100644 index b9fa399..0000000 Binary files a/twitch/examples-de.png and /dev/null differ diff --git a/twitch/examples-en.png b/twitch/examples-en.png deleted file mode 100644 index 7baa629..0000000 Binary files a/twitch/examples-en.png and /dev/null differ diff --git a/twitch/help-de.jpg b/twitch/help-de.jpg new file mode 100644 index 0000000..df3cd2e Binary files /dev/null and b/twitch/help-de.jpg differ diff --git a/twitch/help-en.jpg b/twitch/help-en.jpg new file mode 100644 index 0000000..bfb4939 Binary files /dev/null and b/twitch/help-en.jpg differ diff --git a/twitch/image-descriptions.txt b/twitch/image-descriptions.txt index fbf8844..812cdb4 100644 --- a/twitch/image-descriptions.txt +++ b/twitch/image-descriptions.txt @@ -1,6 +1,9 @@ Sample replacement Text is replaced in the "Say" channel. +Replacements +Define your own set of replacements rules + Replacements Create multiple mappings between search text and replacement with additional conditions. @@ -8,10 +11,13 @@ Channel configuration individual channel activation Profiles -global, per server, per class, per character +global, per server, per class, per character or custom + +Examples +Templates for various situations, now also with import possibility -New: Examples -Templates for various situations +Help +Built-in help texts @@ -28,13 +34,13 @@ Profile: Global, pro Server, pro Klasse, pro Character Neu: Beispiele -Vorlagen für verschiedene Zwecke +Vorlagen für verschiedene Zwecke, jetzt mit Import-Funktion Error Reporting In case of errors please send me the exact error message and please also provide the mapping from "/gri mappings" (see next screen). -Debug Mappings -In case of errors please send me the exact error message and please also provide the mapping from "/gri mappings". +Debug Mappings +In case of errors please send me the exact error message and please also provide the mapping from "/gri mappings" diff --git a/twitch/minimap-icon.png b/twitch/minimap-icon.png index a453e77..2a2cab7 100644 Binary files a/twitch/minimap-icon.png and b/twitch/minimap-icon.png differ diff --git a/twitch/profiles-de.jpg b/twitch/profiles-de.jpg new file mode 100644 index 0000000..8027987 Binary files /dev/null and b/twitch/profiles-de.jpg differ diff --git a/twitch/profiles-de.png b/twitch/profiles-de.png deleted file mode 100644 index 988a978..0000000 Binary files a/twitch/profiles-de.png and /dev/null differ diff --git a/twitch/profiles-en.jpg b/twitch/profiles-en.jpg new file mode 100644 index 0000000..4213ee8 Binary files /dev/null and b/twitch/profiles-en.jpg differ diff --git a/twitch/profiles-en.png b/twitch/profiles-en.png deleted file mode 100644 index 1b1071e..0000000 Binary files a/twitch/profiles-en.png and /dev/null differ diff --git a/twitch/replacements-de.png b/twitch/replacements-de.png deleted file mode 100644 index 9f79bcc..0000000 Binary files a/twitch/replacements-de.png and /dev/null differ diff --git a/twitch/replacements-en.png b/twitch/replacements-en.png deleted file mode 100644 index 6c007c9..0000000 Binary files a/twitch/replacements-en.png and /dev/null differ diff --git a/twitch/replacements1-de.jpg b/twitch/replacements1-de.jpg new file mode 100644 index 0000000..8e07d10 Binary files /dev/null and b/twitch/replacements1-de.jpg differ diff --git a/twitch/replacements1-en.jpg b/twitch/replacements1-en.jpg new file mode 100644 index 0000000..c7e14f9 Binary files /dev/null and b/twitch/replacements1-en.jpg differ diff --git a/twitch/replacements2-de.jpg b/twitch/replacements2-de.jpg new file mode 100644 index 0000000..21bfd44 Binary files /dev/null and b/twitch/replacements2-de.jpg differ diff --git a/twitch/replacements2-en.jpg b/twitch/replacements2-en.jpg new file mode 100644 index 0000000..60d1450 Binary files /dev/null and b/twitch/replacements2-en.jpg differ