Version 0.4.0

- restructured files
- extract functions and color codes
- filter channels
master
Lothar Buchholz 5 years ago
parent b572203dd2
commit cc26683328

@ -8,11 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- case sensitivity - case sensitivity
- consolidate consecutive matches - consolidate consecutive matches
## Version 0.3.1 - 2020-05-28 ## Version 0.5.0 - 2020-05-31
### Added ### Added
- add replacements via options UI - add replacements via options UI
- handle replacement via slash command - handle replacement via slash command
## Version 0.4.0 - 2020-05-30
### Added
- restructured files
- extract functions and color codes
- filter channels
## Version 0.3.0 - 2020-05-27 ## Version 0.3.0 - 2020-05-27
### Fixed ### Fixed
- fixed DB storange and debug printing - fixed DB storange and debug printing

@ -14,32 +14,13 @@
limitations under the License. limitations under the License.
--------------------------------------------------------------------------]] -- --------------------------------------------------------------------------]] --
-- upvalues
local _G = _G
-- faster function lookups by mapping to local refs
local string_find = _G.string.find
local string_gsub = _G.string.gsub
local string_len = _G.string.len
local string_rep = _G.string.rep
local string_sub = _G.string.sub
local string_fmt = _G.string.format
local strtrim = _G.strtrim
local strmatch = _G.strmatch
local tostring = _G.tostring
local tInsert = _G.table.insert
local tContains = _G.tContains
local unpack = _G.unpack
local pairs = _G.pairs
local ipairs = _G.ipairs
-- colors: -- read namespace from global env
local PREFIX_COLOR_CODE = "|c00FFAA00"
-- initialize addon
local AddonName, AddonTable = ... local AddonName, AddonTable = ...
local _G = _G
local Grichelde = LibStub("AceAddon-3.0"):NewAddon(AddonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0") -- initialize addon
local Grichelde = LibStub("AceAddon-3.0"):NewAddon(AddonTable, AddonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0")
Grichelde.L = LibStub("AceLocale-3.0"):GetLocale("Grichelde", true) Grichelde.L = LibStub("AceLocale-3.0"):GetLocale("Grichelde", true)
Grichelde.version = GetAddOnMetadata(AddonName, "Version") Grichelde.version = GetAddOnMetadata(AddonName, "Version")
Grichelde.build = GetAddOnMetadata(AddonName, "X-Build") or "Experimental" Grichelde.build = GetAddOnMetadata(AddonName, "X-Build") or "Experimental"
@ -50,12 +31,6 @@ Grichelde.debug = false
-- publish to global env -- publish to global env
_G.Grichelde = Grichelde _G.Grichelde = Grichelde
Grichelde.config = {
enabled = true,
channels = { "SAY", "EMOTE", "YELL", "PARTY", "GUILD" },
slashCommands = { "/s", "/say", "/e", "/em", "/me", "/emote", "/y", "/yell", "/sh", "/shout", "/p", "/party", "/pl", "/partyleader", "/g", "/gc", "/guild", "/o", "/osay", "/officer", "/raid", "/rsay", "/rl", "/raidleader", "/rw", "/raidwarning", "/i", "/instance", "/bg", "/battleground", "/w", "/whisper", "/t", "/tell", "/send", "/r", "/reply" }
}
-- Ace3 callbacks -- Ace3 callbacks
function Grichelde:OnInitialize() function Grichelde:OnInitialize()
-- Build Interface Options window -- Build Interface Options window
@ -68,7 +43,6 @@ function Grichelde:OnInitialize()
end end
function Grichelde:OnEnable() function Grichelde:OnEnable()
-- Hook in before message is sent to replace all character occurrences where replacements have been defined in the options -- Hook in before message is sent to replace all character occurrences where replacements have been defined in the options
self:RawHook("SendChatMessage", true) self:RawHook("SendChatMessage", true)
@ -84,30 +58,47 @@ function Grichelde:OnDisable()
self:Unhook("SendChatMessage") self:Unhook("SendChatMessage")
end end
--- register 'grichelde' and 'gri' slash commands
function Grichelde:SetupSlashCommands()
local function HandleSlashCommand(input)
-- Show the GUI if no input is supplied, otherwise handle the chat input.
if not input or input:trim() == "" then
LibStub("AceConfigDialog-3.0"):Open(self.name)
else
-- handle slash ourselves
self:Print(self.L.AddonName .. " Version " .. self.version)
self:Print("Handle slash command: " .. input)
end
end
self:RegisterChatCommand("grichelde", HandleSlashCommand)
self:RegisterChatCommand("gri", HandleSlashCommand)
end
--- @param event string --- @param event string
--- @param addonName string --- @param addonName string
function Grichelde:HookIntoForOtherChatAddons(event, addonName) function Grichelde:HookIntoForOtherChatAddons(event, addonName)
if event == "ADDON_LOADED" then if event == "ADDON_LOADED" then
if addonName == "WIM" then if addonName == "WIM" then
WIM.RegisterWidgetTrigger("msg_box", "whisper,chat,w2w", "OnEnterPressed", Grichelde.EditBox_OnEnterPressed) _G.WIM.RegisterWidgetTrigger("msg_box", "whisper,chat,w2w", "OnEnterPressed", Grichelde.EditBox_OnEnterPressed)
-- If available use the WIM API -- If available use the WIM API
if (WIM.RegisterPreSendFilterText) then -- avoid error if WIM not up to date. if (_G.WIM.RegisterPreSendFilterText) then -- avoid error if WIM not up to date.
WIM.RegisterPreSendFilterText(function(text) _G.WIM.RegisterPreSendFilterText(function(text)
return Grichelde:CheckAndReplace(text) return self:CheckAndReplace(text)
end) end)
else else
-- WIM sends its chat messages via the API ChatThrottleLib, which itself hooks the default SendChatMessage api -- WIM sends its chat messages via the API ChatThrottleLib, which itself hooks the default SendChatMessage api
-- many times before Grichelde will. ChatThrottleLib might potentially load before Grichelde, so we just hook -- many times before Grichelde will. ChatThrottleLib might potentially load before Grichelde, so we just hook
-- into ChatThrottleLib to be on the safe side. -- into ChatThrottleLib to be on the safe side.
if (ChatThrottleLib) then if (_G.ChatThrottleLib) then
Grichelde.hooks["ChatThrottleLib"] = ChatThrottleLib.SendChatMessage self.hooks["ChatThrottleLib"] = _G.ChatThrottleLib.SendChatMessage
function ChatThrottleLib:SendChatMessage(prio, prefix, text, ...) function _G.ChatThrottleLib:SendChatMessage(prio, prefix, text, ...)
Grichelde:DebugPrint("ChatThrottleLib:SendChatMessage : Hook called") Grichelde:DebugPrint("ChatThrottleLib:SendChatMessage : Hook called")
local replacedText = Grichelde:CheckAndReplace(text) local replacedText = Grichelde:CheckAndReplace(text)
return Grichelde.hooks["ChatThrottleLib"](ChatThrottleLib, prio, prefix, replacedText, ...) return Grichelde.hooks["ChatThrottleLib"](_G.ChatThrottleLib, prio, prefix, replacedText, ...)
end end
end end
end end
@ -118,327 +109,3 @@ function Grichelde:HookIntoForOtherChatAddons(event, addonName)
end end
end end
end end
--- Before af chat message is sent, check if replacement is required and replace the text accordingly.
--- @param message string
--- @param type string
--- @param language string
--- @param channel string
function Grichelde:SendChatMessage(message, type, language, channel, ...)
local replacedText = self:CheckAndReplace(message, type)
self:DebugPrint("SendChatMessage : replacedText: " .. replacedText)
-- Send text in chunks if length exceeds 255 bytes after replacement
local chunks = self:SplitText(replacedText)
self:DebugPrint("SendChatMessage : #chunks: " .. #chunks)
for _, chunk in ipairs(chunks) do
self.hooks["SendChatMessage"](chunk, type, language, channel, ...);
end
end
function Grichelde:CheckAndReplace(message, type)
local text = message
if (Misspelled) then
self:DebugPrint("Misspelled detected: cleansing message")
text = Misspelled:RemoveHighlighting(text)
end
text = strtrim(text)
if (self:CheckReplacement(text, type)) then
text = self:ReplaceText(text)
end
return text
end
function Grichelde:CheckReplacement(text, type)
if (not Grichelde.config.enabled) then
self:DebugPrint("CheckReplacement : globally disabled")
return false
end
-- check type
if (not tContains(Grichelde.config.channels, type)) then
self:DebugPrint("CheckReplacement : skip channel type")
return false
end
-- don't replace slash commands except chat related commands
if string_sub(text, 1, 1) == "/" then
local firstWord, _ = self:SplitOnFirstMatch(text)
if (firstWord == nil or not tContains(Grichelde.config.slashCommands, firstWord)) then
self:DebugPrint("CheckReplacement : ignore slash command")
return false
end
end
-- in any other case
return true
end
--- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures or raid target icons
--- and returns the end location of the match, or 0 if no pattern was found
--- @param text string
--- @return number
function Grichelde:CheckForPreversableText(text)
self:DebugPrint("CheckForPreversableText : text is " .. text)
-- do not replace these patterns
local ignorePatterns = {
"|[Cc]%x%x%x%x%x%x%x%x.-|r", -- colored items (or links)
"|H.-|h", -- item links (http://www.wowwiki.com/ItemLink)
"|T.-|t", -- textures
"|n", -- newline
"{rt[1-8]}", -- rumbered raid target icons
"{Star}", -- named raid target icon 1
"{Circle}", -- named raid target icon 2
"{Coin}", -- named raid target icon 2
"{Diamond}", -- named raid target icon 3
"{Triangle}", -- named raid target icon 4
"{Moon}", -- named raid target icon 5
"{Square}", -- named raid target icon 6
"{Cross}", -- named raid target icon 7
"{X}", -- named raid target icon 7
"{Skull}" -- named raid target icon 8
}
-- Calling find on ever pattern might be inefficient but its way less code.
for _, pattern in ipairs(ignorePatterns) do
local pos1, pos2 = string_find(text, pattern)
if pos1 == 1 and pos2 ~= nil then
self:DebugPrint("CheckForPreversableText : Found ignore pattern \"%s\" at (%d, %d)", pattern, pos1, pos2)
return pos2
end
end
self:DebugPrint("CheckForPreversableText : no ignore pattern found")
return 0
end
--- Replaces all character occurrences for which replacements have been defined in the options,
--- while preserving any itemLinks or textures. (http://www.wowwiki.com/ItemLink)
--- @param text string
--- @return string
function Grichelde:ReplaceText(text)
local finalText = ""
local newText = text
-- don't replace non-chat related slash commands
local firstWord, line = self:SplitOnFirstMatch(text)
if (firstWord ~= nil and tContains(Grichelde.config.slashCommands, firstWord)) then
self:DebugPrint("ReplaceText : Found slash command %s", firstWord )
-- skip chat slash command
finalText = finalText .. firstWord .. ' '
newText = line
end
local current = 1
local lastStart = 1
while current <= string_len(newText) do
local currentChar = string_sub(newText, current, current)
self:DebugPrint("current/char : %s,%s", current, currentChar)
if currentChar ~= '|' and currentChar ~= '{' then
current = current + 1
else
-- lookahead-check for itemLinks, textures and raid target icons
local textAhead = string_sub(newText, current)
local posEnd = self:CheckForPreversableText(textAhead)
if posEnd > 0 then
self:DebugPrint("ReplaceText : Found an ignore pattern")
local textBehind = string_sub(newText, lastStart, current - 1)
local replacement = self:ReplaceCharacters(textBehind)
local preservedText = string_sub(textAhead, 1, posEnd)
finalText = finalText .. replacement .. preservedText
current = current + posEnd
lastStart = current
self:DebugPrint("ReplaceText : restarting at " .. lastStart)
else
-- no corresponding end was found to start pattern, continue loop with next char
current = current + 1
end
end
end
-- cleanup remaining text to the end
local remainingText = string_sub(newText, lastStart)
local replacement = self:ReplaceCharacters(remainingText)
finalText = finalText .. replacement
self:DebugPrint("ReplaceText : replaced \"" .. text .. "\"")
self:DebugPrint("ReplaceText : with \"" .. finalText .. "\"")
return finalText
end
--- Replaces all character occurrences for which replacements have been defined in the options
--- @param text string
--- @return string
function Grichelde:ReplaceCharacters(text)
-- todo: read from options
-- todo: case (in)sensitivity
-- todo: consolidate consecutive
-- todo: prevent infinite loops - is that even possible?
local replacement = text
replacement = string_gsub(replacement, "s", "ch")
replacement = string_gsub(replacement, "S", "Ch")
replacement = string_gsub(replacement, "t", "k")
replacement = string_gsub(replacement, "T", "K")
self:DebugPrint("ReplaceCharacters : replaced \"%s\" with \"%s\"", text, replacement)
return replacement
end
--- Splits a long text in longest possible chunks of <= 255 length, split at last available space
--- @param text string
--- @return table
function Grichelde:SplitText(text)
local chunks = {}
local splitText = text
local textSize = string_len(splitText)
while textSize > 255 do
local chunk = string_sub(splitText, 1, 255)
local remaining = ""
-- special case: if space is the start of the next chunk, don't split this chunk
if string_sub(splitText, 256, 256) ~= ' ' then
-- split at last space, don't assign directly as nil might be returned
local left, right = self:SplitOnLastMatch(chunk)
if left ~= nil then
chunk = left
end
if right ~= nil then
remaining = right
end
end
self:DebugPrint("SplitText : chunk: " .. chunk )
tInsert(chunks, chunk)
splitText = remaining .. string_sub(splitText, 256)
textSize = string_len(splitText)
end
-- pickup remaining text < 255
self:DebugPrint("SplitText : last chunk: " .. splitText)
tInsert(chunks, splitText)
return chunks
end
-- split first word of a text line
function Grichelde:SplitOnFirstMatch(text, start)
self:DebugPrint("SplitOnFirstMatch : text: %s, start: %d", text, start)
local pos = start or 1
local left, right = strmatch(text, "^.- .+", pos)
self:DebugPrint("SplitOnFirstMatch : left: %s, right: %s", left, right)
return left, right
end
function Grichelde:SplitOnLastMatch(text, start)
self:DebugPrint("SplitOnLastMatch : text: %s, start: %d", text, start)
local pos = start or 1
local left, right = strmatch(text, ".+ .-$", pos)
self:DebugPrint("SplitOnLastMatch : left: %s, right: %s", left, right)
return left, right
end
function Grichelde:Format(message,...)
local msg = message
local l = select("#", ...)
if l > 0 then
-- sanitize nil values in vararg
local packed = {...}
for i = 1,l do
packed[i] = packed[i] or "nil"
end
-- print("packed = ", packed)
-- self:tPrint(packed)
-- cannot assign unpacked to a vararg variable and print it for debug
msg = string_fmt(message, unpack(packed))
end
return msg or "nil"
end
function Grichelde:Print(...)
print(self:Format(...))
end
local function prefixedPrint(colorCode, prefix, endClose, ...)
print(colorCode .. prefix .. endClose .. ": " .. ...)
end
function Grichelde:PrefixedPrint(...)
prefixedPrint(PREFIX_COLOR_CODE, self.L.AddonName, _G.FONT_COLOR_CODE_CLOSE, self:Format(...))
end
function Grichelde:DebugPrint(...)
if (self.debug) then
prefixedPrint(_G.GRAY_FONT_COLOR_CODE, self.L.AddonName, _G.FONT_COLOR_CODE_CLOSE, self:Format(...))
end
end
local function tLen(t)
local count = 0
for _ in pairs(t) do count = count + 1 end
return count
end
-- show strings differently to distinguish them from numbers
function Grichelde:PlainValue(val)
if val == nil then
return "<nil>"
elseif type(val) == "string" then
return '"' .. val .. '"'
elseif type(val) == "table" then
if tLen(val) > 0 then
return tostring(val)
else
return "{}"
end
else
return tostring(val)
end
end
--- Prints any value to default channel, do NOT return a string.
function Grichelde:tPrint(val, indent, known)
if (not self.debug) then return end
indent = indent or 0
known = known or {}
if val == nil then
print(string_rep(" ", indent) .. "<nil>")
elseif type(val) == "string" then
print(string_rep(" ", indent) .. "\"" .. val .. "\"")
elseif type(val) == "table" then
if tLen(val) > 0 then
for key, value in pairs(val) do
if value == nil then
print(string_rep(" ", indent) .. self:PlainValue(key) .. "= <nil>")
elseif type(value) == "table" then
print(string_rep(" ", indent) .. self:PlainValue(key) .. "= {")
if tLen(value) > 0 then
if not known[value] then
self:tPrint(value, indent + 4, known)
known[value] = true
else
print("<known table> " .. self:PlainValue(value))
end
end
print(string_rep(" ", indent) .. "}")
else
print(string_rep(" ", indent) .. self:PlainValue(key) .. " = ".. self:PlainValue(value))
end
end
else
print(string_rep(" ", indent) .. "{}")
end
else
print(string_rep(" ", indent) .. tostring(val))
end
end

@ -3,7 +3,7 @@
## Title: Grichelde ## Title: Grichelde
## Notes: Replaces characters you type in the chat box ## Notes: Replaces characters you type in the chat box
## Notes-de: Ersetzt eingegebene Zeichen in der Chat-Zeile ## Notes-de: Ersetzt eingegebene Zeichen in der Chat-Zeile
## Version: 0.3.0 ## Version: 0.4.0
## Author: Teilzeit-Jedi ## Author: Teilzeit-Jedi
## eMail: tj@teilzeit-jedi.de ## eMail: tj@teilzeit-jedi.de
@ -20,4 +20,7 @@ libs.xml
localisation.xml localisation.xml
Grichelde.lua Grichelde.lua
GricheldeConstants.lua
GricheldeUtils.lua
GricheldeChat.lua
GricheldeOptions.lua GricheldeOptions.lua

@ -0,0 +1,245 @@
-- import addon read namespace from global env
local _G = _G
local Grichelde = _G.Grichelde
local ipairs, tContains, tFilter, tInsert, tConcat, find, sub, gsub, match, toLower, trim, length
= Grichelde.functions.ipairs, Grichelde.functions.tContains, Grichelde.functions.tFilter, Grichelde.functions.tInsert, Grichelde.functions.tConcat, Grichelde.functions.find, Grichelde.functions.sub, Grichelde.functions.gsub, Grichelde.functions.match, Grichelde.functions.toLower, Grichelde.functions.trim, Grichelde.functions.length
--- Before a chat message is sent, check if replacement is required and replace the text accordingly.
--- @param message string
--- @param type string
--- @param language string
--- @param channel string
function Grichelde:SendChatMessage(message, type, language, channel, ...)
local replacedText = self:CheckAndReplace(message, type)
self:DebugPrint("SendChatMessage : replacedText: " .. replacedText)
-- Send text in chunks if length exceeds 255 bytes after replacement
local chunks = self:SplitText(replacedText)
self:DebugPrint("SendChatMessage : #chunks: " .. #chunks)
for _, chunk in ipairs(chunks) do
self.hooks["SendChatMessage"](chunk, type, language, channel, ...);
end
end
function Grichelde:CheckAndReplace(message, type)
local text = message
if (self:CheckReplacement(text, type)) then
if (_G.Misspelled) then
self:DebugPrint("Misspelled detected: cleansing message")
text = _G.Misspelled:RemoveHighlighting(text)
end
text = self:ReplaceText(trim(text))
end
return text
end
function Grichelde:CheckReplacement(text, channel)
if (not self.db.profile.enabled) then
self:DebugPrint("CheckReplacement : disabled")
return false
end
-- check channel type
local allowedChannels = tFilter(self.db.profile.channels,
function(_,v) return v == true end,
function(k,_) return k end
)
self:DebugPrint("CheckReplacement : allowed channels: %s", tConcat(allowedChannels, ", "))
local type = self:ConvertBlizChannelToType(channel)
if (type ~= nil and not tContains(allowedChannels, type)) then
self:DebugPrint("CheckReplacement : skip channel type %s", type)
return false
end
-- don't replace slash commands except chat related commands
if sub(text, 1, 1) == "/" then
local firstWord, _ = self:SplitOnFirstMatch(text)
-- todo: adapt allowed slash commands
if (firstWord == nil or not tContains(self.slashCommands, firstWord)) then
self:DebugPrint("CheckReplacement : ignore slash command")
return false
end
end
-- in any other case
return true
end
function Grichelde:ConvertBlizChannelToType(channel)
local type = toLower(channel)
self:DebugPrint("ConvertBlizChannelToType : convert %s to %s", channel, type)
return type
end
--- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures or raid target icons
--- and returns the end location of the match, or 0 if no pattern was found
--- @param text string
--- @return number
function Grichelde:CheckForPreversableText(text)
self:DebugPrint("CheckForPreversableText : text is " .. text)
-- do not replace these patterns
local ignorePatterns = {
"|[Cc]%x%x%x%x%x%x%x%x.-|r", -- colored items (or links)
"|H.-|h", -- item links (http://www.wowwiki.com/ItemLink)
"|T.-|t", -- textures
"|n", -- newline
"{rt[1-8]}", -- rumbered raid target icons
"{Star}", -- named raid target icon 1
"{Circle}", -- named raid target icon 2
"{Coin}", -- named raid target icon 2
"{Diamond}", -- named raid target icon 3
"{Triangle}", -- named raid target icon 4
"{Moon}", -- named raid target icon 5
"{Square}", -- named raid target icon 6
"{Cross}", -- named raid target icon 7
"{X}", -- named raid target icon 7
"{Skull}" -- named raid target icon 8
}
-- Calling find on ever pattern might be inefficient but its way less code.
for _, pattern in ipairs(ignorePatterns) do
local pos1, pos2 = find(text, pattern)
if pos1 == 1 and pos2 ~= nil then
self:DebugPrint("CheckForPreversableText : Found ignore pattern \"%s\" at (%d, %d)", pattern, pos1, pos2)
return pos2
end
end
self:DebugPrint("CheckForPreversableText : no ignore pattern found")
return 0
end
--- Replaces all character occurrences for which replacements have been defined in the options,
--- while preserving any itemLinks or textures. (http://www.wowwiki.com/ItemLink)
--- @param text string
--- @return string
function Grichelde:ReplaceText(text)
local finalText = ""
local newText = text
-- don't replace non-chat related slash commands
local firstWord, line = self:SplitOnFirstMatch(text)
if (firstWord ~= nil and tContains(self.slashCommands, firstWord)) then
self:DebugPrint("ReplaceText : Found slash command %s", firstWord )
-- skip chat slash command
finalText = finalText .. firstWord .. ' '
newText = line
end
local current = 1
local lastStart = 1
while current <= length(newText) do
local currentChar = sub(newText, current, current)
self:DebugPrint("current/char : %s,%s", current, currentChar)
if currentChar ~= '|' and currentChar ~= '{' then
current = current + 1
else
-- lookahead-check for itemLinks, textures and raid target icons
local textAhead = sub(newText, current)
local posEnd = self:CheckForPreversableText(textAhead)
if posEnd > 0 then
self:DebugPrint("ReplaceText : Found an ignore pattern")
local textBehind = sub(newText, lastStart, current - 1)
local replacement = self:ReplaceCharacters(textBehind)
local preservedText = sub(textAhead, 1, posEnd)
finalText = finalText .. replacement .. preservedText
current = current + posEnd
lastStart = current
self:DebugPrint("ReplaceText : restarting at " .. lastStart)
else
-- no corresponding end was found to start pattern, continue loop with next char
current = current + 1
end
end
end
-- cleanup remaining text to the end
local remainingText = sub(newText, lastStart)
local replacement = self:ReplaceCharacters(remainingText)
finalText = finalText .. replacement
self:DebugPrint("ReplaceText : replaced \"" .. text .. "\"")
self:DebugPrint("ReplaceText : with \"" .. finalText .. "\"")
return finalText
end
--- Replaces all character occurrences for which replacements have been defined in the options
--- @param text string
--- @return string
function Grichelde:ReplaceCharacters(text)
-- todo: read from options
-- todo: case (in)sensitivity
-- todo: consolidate consecutive
-- todo: prevent infinite loops - is that even possible?
local replacement = text
replacement = gsub(replacement, "s", "ch")
replacement = gsub(replacement, "S", "Ch")
replacement = gsub(replacement, "t", "k")
replacement = gsub(replacement, "T", "K")
self:DebugPrint("ReplaceCharacters : replaced \"%s\" with \"%s\"", text, replacement)
return replacement
end
--- Splits a long text in longest possible chunks of <= 255 length, split at last available space
--- @param text string
--- @return table
function Grichelde:SplitText(text)
local chunks = {}
local splitText = text
local textSize = length(splitText)
while textSize > 255 do
local chunk = sub(splitText, 1, 255)
local remaining = ""
-- special case: if space is the start of the next chunk, don't split this chunk
if sub(splitText, 256, 256) ~= ' ' then
-- split at last space, don't assign directly as nil might be returned
local left, right = self:SplitOnLastMatch(chunk)
if left ~= nil then
chunk = left
end
if right ~= nil then
remaining = right
end
end
self:DebugPrint("SplitText : chunk: " .. chunk )
tInsert(chunks, chunk)
splitText = remaining .. sub(splitText, 256)
textSize = length(splitText)
end
-- pickup remaining text < 255
self:DebugPrint("SplitText : last chunk: " .. splitText)
tInsert(chunks, splitText)
return chunks
end
-- split at first word of a text line
function Grichelde:SplitOnFirstMatch(text, start)
self:DebugPrint("SplitOnFirstMatch : text: %s, start: %d", text, start)
local pos = start or 1
local left, right = match(text, "^.- .+", pos)
self:DebugPrint("SplitOnFirstMatch : left: %s, right: %s", left, right)
return left or text, right
end
-- split at last word of a text line
function Grichelde:SplitOnLastMatch(text, start)
self:DebugPrint("SplitOnLastMatch : text: %s, start: %d", text, start)
local pos = start or 1
local left, right = match(text, ".+ .-$", pos)
self:DebugPrint("SplitOnLastMatch : left: %s, right: %s", left, right)
return left, right or text
end

@ -0,0 +1,94 @@
-- read namespace from global env
local _G = _G
local Grichelde = _G.Grichelde
-- upvalues and constants
-- faster function lookups by mapping to local refs
Grichelde.functions = {}
Grichelde.functions.type = _G.type
Grichelde.functions.print = _G.print
Grichelde.functions.pairs = _G.pairs
Grichelde.functions.ipairs = _G.ipairs
Grichelde.functions.tContains = _G.tContains
Grichelde.functions.tFilter = function(t, cond, extr)
local filtered = {}
for key, value in Grichelde.functions.pairs(t) do
if cond(key, value) then
local val = extr(key, value)
Grichelde.functions.tInsert(filtered, #filtered + 1, val)
end
end
return filtered
end
Grichelde.functions.tInsert = _G.table.insert
Grichelde.functions.tConcat = _G.table.concat
Grichelde.functions.select = _G.select
Grichelde.functions.unpack = _G.unpack
Grichelde.functions.find = _G.string.find
Grichelde.functions.sub = _G.string.sub
Grichelde.functions.gsub = _G.string.gsub
Grichelde.functions.match = _G.strmatch
Grichelde.functions.join = _G.strjoin
Grichelde.functions.toLower = _G.strlower
Grichelde.functions.toUpper = _G.strupper
Grichelde.functions.format = _G.string.format
Grichelde.functions.rep = _G.string.rep
Grichelde.functions.trim = _G.strtrim
Grichelde.functions.length = _G.string.len
Grichelde.functions.toString = _G.tostring
-- colors:
Grichelde.COLOR_CODES = {}
Grichelde.COLOR_CODES.PREFIX = "|c00FFAA00"
-- https://github.com/stoneharry/Misc-WoW-Stuff/blob/master/EoC%20Interface/FrameXML/Constants.lua
Grichelde.COLOR_CODES.NORMAL = _G.NORMAL_FONT_COLOR_CODE or "|cffffd200";
Grichelde.COLOR_CODES.HIGHLIGHT = _G.HIGHLIGHT_FONT_COLOR_CODE or "|cffffffff";
Grichelde.COLOR_CODES.RED = _G.RED_FONT_COLOR_CODE or "|cffff2020";
Grichelde.COLOR_CODES.GREEN = _G.GREEN_FONT_COLOR_CODE or "|cff20ff20";
Grichelde.COLOR_CODES.GRAY = _G.GRAY_FONT_COLOR_CODE or "|cff808080";
Grichelde.COLOR_CODES.YELLOW = _G.YELLOW_FONT_COLOR_CODE or "|cffffff00";
Grichelde.COLOR_CODES.LIGHTYELLOW = _G.LIGHTYELLOW_FONT_COLOR_CODE or "|cffffff9a";
Grichelde.COLOR_CODES.ORANGE = _G.ORANGE_FONT_COLOR_CODE or "|cffff7f3f";
Grichelde.COLOR_CODES.CLOSE = _G.FONT_COLOR_CODE_CLOSE or "|r";
Grichelde.slashCommands = { "/s", "/say", "/e", "/em", "/me", "/emote", "/y", "/yell", "/sh", "/shout", "/p", "/party", "/pl", "/partyleader", "/g", "/gc", "/guild", "/o", "/osay", "/officer", "/raid", "/rsay", "/rl", "/raidleader", "/rw", "/raidwarning", "/i", "/instance", "/bg", "/battleground", "/w", "/whisper", "/t", "/tell", "/send", "/r", "/reply" }
Grichelde.defaultConfig = {
global = {},
profile = {
enabled = true,
channels = {
["*"] = false,
say = true,
emote = false,
yell = true,
party = true,
partyLeader = true,
guild = true,
officer = true,
},
replacements = {
["**"] = {
searchText = "",
replaceText = "",
caseSensitive = false,
consolidate = true,
},
replacement_0 = {
order = 1,
searchText = "s",
replaceText = "ch",
caseSensitive = false,
consolidate = true,
},
replacement_1 = {
order = 2,
searchText = "t",
replaceText = "ck",
caseSensitive = false,
consolidate = true,
}
}
}
}

@ -1,4 +1,8 @@
local AddonName, _ = ... -- read namespace from global env
local _G = _G
local Grichelde = _G.Grichelde
local unpack, join, toString = Grichelde.functions.unpack, Grichelde.functions.join, Grichelde.functions.toString
function Grichelde:CreateOptionsUI() function Grichelde:CreateOptionsUI()
return { return {
@ -15,8 +19,6 @@ function Grichelde:CreateOptionsUI()
type = "toggle", type = "toggle",
name = self.L.Options_Enabled_Name, name = self.L.Options_Enabled_Name,
desc = self:Format(self.L.Options_Enabled_Desc, self.L.AddonName), desc = self:Format(self.L.Options_Enabled_Desc, self.L.AddonName),
set = function(info, val) self.db.profile.enabled=val end,
get = function(info) return self.db.profile.enabled end,
disabled = false, disabled = false,
}, },
@ -80,20 +82,26 @@ function Grichelde:CreateOptionsUI()
name = self.L.Options_Channels_ChannelRaidLeader_Name, name = self.L.Options_Channels_ChannelRaidLeader_Name,
desc = self:Format(self.L.Options_Channels_ChannelRaidLeader_Desc, self.L.AddonName), desc = self:Format(self.L.Options_Channels_ChannelRaidLeader_Desc, self.L.AddonName),
}, },
instance = { raidWarning = {
order = 9, order = 9,
type = "toggle", type = "toggle",
name = self.L.Options_Channels_ChannelRaidWarning_Name,
desc = self:Format(self.L.Options_Channels_ChannelRaidWarning_Desc, self.L.AddonName),
},
instance = {
order = 10,
type = "toggle",
name = self.L.Options_Channels_ChannelInstance_Name, name = self.L.Options_Channels_ChannelInstance_Name,
desc = self:Format(self.L.Options_Channels_ChannelInstance_Desc, self.L.AddonName), desc = self:Format(self.L.Options_Channels_ChannelInstance_Desc, self.L.AddonName),
}, },
battleground = { battleground = {
order = 10, order = 11,
type = "toggle", type = "toggle",
name = self.L.Options_Channels_ChannelBattleground_Name, name = self.L.Options_Channels_ChannelBattleground_Name,
desc = self:Format(self.L.Options_Channels_ChannelBattleground_Desc, self.L.AddonName), desc = self:Format(self.L.Options_Channels_ChannelBattleground_Desc, self.L.AddonName),
}, },
whisper = { whisper = {
order = 11, order = 12,
type = "toggle", type = "toggle",
name = self.L.Options_Channels_ChannelWhisper_Name, name = self.L.Options_Channels_ChannelWhisper_Name,
desc = self:Format(self.L.Options_Channels_ChannelWhisper_Desc, self.L.AddonName), desc = self:Format(self.L.Options_Channels_ChannelWhisper_Desc, self.L.AddonName),
@ -167,51 +175,20 @@ function Grichelde:CreateOptionsUI()
} }
end end
local Grichelde_DefaultConfig = {
global = {},
profile = {
enabled = true,
channels = {
["*"] = false,
say = true,
emote = false,
yell = true,
party = true,
partyLeader = true,
guild = true,
officer = true,
},
replacements = {
["**"] = {
searchText = "",
replaceText = "",
caseSensitive = false,
consolidate = true,
},
replacement_0 = {
order = 1,
searchText = "s",
replaceText = "ch",
caseSensitive = false,
consolidate = true,
},
replacement_1 = {
order = 2,
searchText = "t",
replaceText = "ck",
caseSensitive = false,
consolidate = true,
}
}
}
}
function Grichelde:LoadDatabase() function Grichelde:LoadDatabase()
-- Called when the addon is loaded -- Called when the addon is loaded
self.db = LibStub("AceDB-3.0"):New(AddonName .."DB", Grichelde_DefaultConfig, true) self.db = LibStub("AceDB-3.0"):New(self.name .."DB", self.defaultConfig, true)
self.db.RegisterCallback(self, "OnProfileChanged", "RefreshConfig")
self.db.RegisterCallback(self, "OnProfileCopied", "RefreshConfig") -- todo: is this really needed?
self.db.RegisterCallback(self, "OnProfileReset", "RefreshConfig") self.db.RegisterCallback(self, "OnProfileChanged", function (event)
self:PrefixedPrint(self.L.Profiles_Loaded, self.COLOR_CODES.GREEN .. self.db:GetCurrentProfile() .. self.COLOR_CODES.CLOSE)
end)
self.db.RegisterCallback(self, "OnProfileCopied", function(event)
self:PrefixedPrint(self.L.Profiles_Copied, self.COLOR_CODES.GREEN .. self.db:GetCurrentProfile() .. self.COLOR_CODES.CLOSE)
end)
self.db.RegisterCallback(self, "OnProfileReset", function(event)
self:PrefixedPrint(self.L.Profiles_Reset, self.COLOR_CODES.GREEN .. self.db:GetCurrentProfile() .. self.COLOR_CODES.CLOSE)
end)
end end
function Grichelde:SetupOptions() function Grichelde:SetupOptions()
@ -221,27 +198,12 @@ function Grichelde:SetupOptions()
self.options.args.profiles.disabled = false self.options.args.profiles.disabled = false
local activeProfile = self.db:GetCurrentProfile() local activeProfile = self.db:GetCurrentProfile()
self:PrefixedPrint(self.L.Profiles_Loaded, _G.GREEN_FONT_COLOR_CODE, activeProfile, "|r") self:PrefixedPrint(self.L.Profiles_Loaded, self.COLOR_CODES.GREEN .. activeProfile .. self.COLOR_CODES.CLOSE)
self:tPrint(self.db.profile) self:tPrint(self.db.profile)
-- Adding options to blizzard frame -- Adding options to blizzard frame
LibStub("AceConfig-3.0"):RegisterOptionsTable(AddonName, self.options) LibStub("AceConfig-3.0"):RegisterOptionsTable(self.name, self.options)
LibStub("AceConfigDialog-3.0"):AddToBlizOptions(AddonName, self.L.AddonName) LibStub("AceConfigDialog-3.0"):AddToBlizOptions(self.name, self.L.AddonName)
end
function Grichelde:SetupSlashCommands()
local function HandleSlashCommand(input)
-- Show the GUI if no input is supplied, otherwise handle the chat input.
if not input or input:trim() == "" then
LibStub("AceConfigDialog-3.0"):Open(AddonName)
else
-- handle slash ourselves
self:Print("Handle slash command: " .. input)
end
end
self:RegisterChatCommand("grichelde", HandleSlashCommand)
self:RegisterChatCommand("gri", HandleSlashCommand)
end end
function Grichelde:SyncToDatabase(info, val) function Grichelde:SyncToDatabase(info, val)
@ -251,8 +213,8 @@ function Grichelde:SyncToDatabase(info, val)
option = option[info[path]] -- or nil option = option[info[path]] -- or nil
path = path + 1 path = path + 1
end end
local optionPath = strjoin(".", unpack(info, 1, #info)) local optionPath = join(".", unpack(info, 1, #info))
self:DebugPrint("change option \"%s\" from %s to %s", optionPath, tostring(option[info[path]]), tostring(val)) self:DebugPrint("change option \"%s\" from %s to %s", optionPath, toString(option[info[path]]), toString(val))
option[info[path]] = val option[info[path]] = val
end end
@ -263,8 +225,8 @@ function Grichelde:ReadFromDatabase(info)
option = option[info[path]] -- or nil option = option[info[path]] -- or nil
path = path + 1 path = path + 1
end end
local optionPath = strjoin(".", unpack(info, 1, #info)) local optionPath = join(".", unpack(info, 1, #info))
self:DebugPrint("read option \"%s\": %s", optionPath, tostring(option)) self:DebugPrint("read option \"%s\": %s", optionPath, toString(option))
return option return option
end end
@ -274,7 +236,3 @@ function Grichelde:IsDisabled(info)
end end
return not self.db.profile.enabled return not self.db.profile.enabled
end end
function Grichelde:RefreshConfig(event)
self:Print(self.L.Profiles_Refreshed, _G.GREEN_FONT_COLOR_CODE, self.db:GetCurrentProfile(), "|r")
end

@ -0,0 +1,103 @@
-- import addon read namespace from global env
local _G = _G
local Grichelde = _G.Grichelde
local type, print, pairs, select, unpack, format, rep, toString
= Grichelde.functions.type, Grichelde.functions.print, Grichelde.functions.pairs, Grichelde.functions.select, Grichelde.functions.unpack, Grichelde.functions.format, Grichelde.functions.rep, Grichelde.functions.toString
function Grichelde:Format(message, ...)
local msg = message
local l = select("#", ...)
if l > 0 then
-- sanitize nil values in vararg
local packed = { ... }
for i = 1, l do
packed[i] = toString(packed[i]) or "nil"
end
-- print("packed = ", packed)
-- self:tPrint(packed)
-- cannot assign unpacked to a vararg variable and print it for debug
msg = format(message, unpack(packed))
end
return msg or "nil"
end
function Grichelde:Print(...)
print(self:Format(...))
end
local function prefixedPrint(colorCode, prefix, endClose, ...)
print(colorCode .. prefix .. endClose .. ": " .. ...)
end
function Grichelde:PrefixedPrint(...)
prefixedPrint(self.COLOR_CODES.PREFIX, self.L.AddonName, self.COLOR_CODES.CLOSE, self:Format(...))
end
function Grichelde:DebugPrint(...)
if (self.debug) then
prefixedPrint(self.COLOR_CODES.GRAY, self.L.AddonName, self.COLOR_CODES.CLOSE, self:Format(...))
end
end
local function tLen(t)
local count = 0
for _ in pairs(t) do count = count + 1 end
return count
end
-- show strings differently to distinguish them from numbers
function Grichelde:PlainValue(val)
if val == nil then
return "<nil>"
elseif type(val) == "string" then
return '"' .. val .. '"'
elseif type(val) == "table" then
if tLen(val) > 0 then
return toString(val)
else
return "{}"
end
else
return toString(val)
end
end
--- Prints any value to default channel, do NOT return a string.
function Grichelde:tPrint(val, indent, known)
if (not self.debug) then return end
indent = indent or 0
known = known or {}
if val == nil then
print(rep(" ", indent) .. "<nil>")
elseif type(val) == "string" then
print(rep(" ", indent) .. "\"" .. val .. "\"")
elseif type(val) == "table" then
if tLen(val) > 0 then
for key, value in pairs(val) do
if value == nil then
print(rep(" ", indent) .. self:PlainValue(key) .. "= <nil>")
elseif type(value) == "table" then
print(rep(" ", indent) .. self:PlainValue(key) .. "= {")
if tLen(value) > 0 then
if not known[value] then
self:tPrint(value, indent + 4, known)
known[value] = true
else
print("<known table> " .. self:PlainValue(value))
end
end
print(rep(" ", indent) .. "}")
else
print(rep(" ", indent) .. self:PlainValue(key) .. " = " .. self:PlainValue(value))
end
end
else
print(rep(" ", indent) .. "{}")
end
else
print(rep(" ", indent) .. toString(val))
end
end

@ -9,13 +9,13 @@ L.Addon_Detected_WIM = "Das Addon 'WIM' wurde erkannt und alle Flüsternnachrich
-- profiles -- profiles
L.Profiles_Available = "Verf\195\188gbare Profile:" L.Profiles_Available = "Verf\195\188gbare Profile:"
L.Profiles_Created = "Neues Profil %s%s%s angelegt." L.Profiles_Created = "Neues Profil %s angelegt."
L.Profiles_Loaded = "Profil %s%s%s geladen." L.Profiles_Loaded = "Profil %s geladen."
L.Profiles_Refreshed = "Profil %s%s%s aktualisiert." L.Profiles_Refreshed = "Profil %s aktualisiert."
L.Profiles_Deleted = "Profil %s%s%s gel\195\182scht." L.Profiles_Deleted = "Profil %s gel\195\182scht."
L.Profiles_Copied = "Einstellungen von Profil %s%s%s \195\188bernommen." L.Profiles_Copied = "Einstellungen von Profil %s \195\188bernommen."
L.Profiles_Reset = "Profil %s%s%s zur\195\188ckgesetzt." L.Profiles_Reset = "Profil %s zur\195\188ckgesetzt."
L.Profiles_Invalid = "Ung\195\188ltiges Profil %s%s%s!" L.Profiles_Invalid = "Ung\195\188ltiges Profil %s!"
L.Profiles_DeleteError = "Das aktive Profil kann nicht gel\195\182scht werden!" L.Profiles_DeleteError = "Das aktive Profil kann nicht gel\195\182scht werden!"
-- options -- options
@ -43,6 +43,8 @@ L.Options_Channels_ChannelRaid_Name = "Schlachtzug"
L.Options_Channels_ChannelRaid_Desc = "Aktiviert %s im Kanal \"Schlachtzug\"." L.Options_Channels_ChannelRaid_Desc = "Aktiviert %s im Kanal \"Schlachtzug\"."
L.Options_Channels_ChannelRaidLeader_Name = "Schlachtzugsanf\195\188hrer" L.Options_Channels_ChannelRaidLeader_Name = "Schlachtzugsanf\195\188hrer"
L.Options_Channels_ChannelRaidLeader_Desc = "Aktiviert %s im Kanal \"Schlachtzugsanf\195\188hrer\"." L.Options_Channels_ChannelRaidLeader_Desc = "Aktiviert %s im Kanal \"Schlachtzugsanf\195\188hrer\"."
L.Options_Channels_ChannelRaidWarning_Name = "Schlachtzugswarnung"
L.Options_Channels_ChannelRaidWarning_Desc = "Aktiviert %s im Kanal \"Schlachtzugswarnung."
L.Options_Channels_ChannelInstance_Name = "Instanz" L.Options_Channels_ChannelInstance_Name = "Instanz"
L.Options_Channels_ChannelInstance_Desc = "Aktiviert %s im Kanal \"Instanz\"." L.Options_Channels_ChannelInstance_Desc = "Aktiviert %s im Kanal \"Instanz\"."
L.Options_Channels_ChannelBattleground_Name = "Schlachtfeld" L.Options_Channels_ChannelBattleground_Name = "Schlachtfeld"

@ -10,13 +10,13 @@ L.Addon_Detected_WIM = "Das Addon 'WIM' has been detected and any whispers will
-- profiles -- profiles
L.Profiles_Available = "Available profiles:" L.Profiles_Available = "Available profiles:"
L.Profiles_Created = "New profile \"%s\" created." L.Profiles_Created = "New profile %s created."
L.Profiles_Loaded = "Profile %s%s%s is loaded." L.Profiles_Loaded = "Profile %s is loaded."
L.Profiles_Refreshed = "Profil %s%s%s refreshed." L.Profiles_Refreshed = "Profil %s refreshed."
L.Profiles_Deleted = "Profile %s%s%s deleted." L.Profiles_Deleted = "Profile %s deleted."
L.Profiles_Copied = "Settings applied from profile %s%s%s." L.Profiles_Copied = "Settings applied from profile %s."
L.Profiles_Reset = "Profil %s%s%s reset." L.Profiles_Reset = "Profil %s reset."
L.Profiles_Invalid = "Invalid profile %s%s%s!" L.Profiles_Invalid = "Invalid profile %s!"
L.Profiles_DeleteError = "The active profile cannot be deleted!" L.Profiles_DeleteError = "The active profile cannot be deleted!"
-- options -- options
@ -44,6 +44,8 @@ L.Options_Channels_ChannelRaid_Name = "Raid"
L.Options_Channels_ChannelRaid_Desc = "Activates %s in channel \"Raid\"." L.Options_Channels_ChannelRaid_Desc = "Activates %s in channel \"Raid\"."
L.Options_Channels_ChannelRaidLeader_Name = "Raid Leader" L.Options_Channels_ChannelRaidLeader_Name = "Raid Leader"
L.Options_Channels_ChannelRaidLeader_Desc = "Activates %s in channel \"Raid Leader\"." L.Options_Channels_ChannelRaidLeader_Desc = "Activates %s in channel \"Raid Leader\"."
L.Options_Channels_ChannelRaidWarning_Name = "Raid Warning"
L.Options_Channels_ChannelRaidWarning_Desc = "Activates %s in channel \"Raid Warning\"."
L.Options_Channels_ChannelInstance_Name = "Instance" L.Options_Channels_ChannelInstance_Name = "Instance"
L.Options_Channels_ChannelInstance_Desc = "Activates %s in channel \"Instance\"." L.Options_Channels_ChannelInstance_Desc = "Activates %s in channel \"Instance\"."
L.Options_Channels_ChannelBattleground_Name = "Battleground" L.Options_Channels_ChannelBattleground_Name = "Battleground"

Loading…
Cancel
Save