--- **AceConsole-3.0** provides registration facilities for slash commands. -- You can register slash commands to your custom functions and use the `GetArgs` function to parse them -- to your addons individual needs. -- -- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by -- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object -- and can be accessed directly, without having to explicitly call AceConsole itself.\\ -- It is recommended to embed AceConsole, otherwise you'll have to specify a custom `self` on all calls you -- make into AceConsole. -- @class file -- @name AceConsole-3.0 -- @release $Id: AceConsole-3.0.lua 1143 2016-07-11 08:52:03Z nevcairiel $ local MAJOR,MINOR = "AceConsole-3.0", 7 local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceConsole then return end -- No upgrade needed local AceCore = LibStub("AceCore-3.0") AceConsole.embeds = AceConsole.embeds or {} -- table containing objects AceConsole is embedded in. AceConsole.commands = AceConsole.commands or {} -- table containing commands registered AceConsole.weakcommands = AceConsole.weakcommands or {} -- table containing self, command => func references for weak commands that don't persist through enable/disable -- Lua APIs local tinsert, tconcat, tgetn, tsetn = table.insert, table.concat, table.getn, table.setn local tostring = tostring local type, pairs, error = type, pairs, error local format, strfind, strsub = string.format, string.find, string.sub local max = math.max local strupper, strlower = string.upper, string.lower -- WoW APIs local _G = AceCore._G -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded -- List them here for Mikk's FindGlobals script -- GLOBALS: DEFAULT_CHAT_FRAME, SlashCmdList, hash_SlashCmdList local Print do local tmp = {} function Print(self, frame, arg) if self ~= AceConsole then tmp[1] = "|cff33ff99"..tostring(self).."|r:" else tmp[1] = '' end if type(arg) == "string" then frame:AddMessage(tmp[1]..arg) else -- arg is table and may contain frame as first element if argument frame is nil local b, e = frame and 1 or 2, tgetn(arg) if e >= b then frame = frame or arg[1] for i=0,e-b do tmp[2+i] = tostring(arg[b+i]) end frame:AddMessage(tconcat(tmp," ",1,e-b+2)) -- explicitly, because the length is not affected by assignment end end end end -- Print --- Print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function) -- @paramsig [chatframe ,] ... -- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function) -- @param ... List of any values to be printed function AceConsole:Print(...) local frame = arg[1] if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member? return Print(self, nil, arg) else return Print(self, DEFAULT_CHAT_FRAME, arg) end end --- Formatted (using format()) print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function) -- @paramsig [chatframe ,] "format"[, ...] -- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function) -- @param format Format string - same syntax as standard Lua format() -- @param ... Arguments to the format string function AceConsole:Printf(a1, ...) local frame, succ, s if type(a1) == "table" and a1.AddMessage then -- Is first argument something with an .AddMessage member? frame, succ, s = a1, pcall(format, unpack(arg)) else frame, succ, s = DEFAULT_CHAT_FRAME, pcall(format, a1, unpack(arg)) end if not succ then error(s,2) end return Print(self, frame, s) end --- Register a simple chat command -- @param command Chat command to be registered WITHOUT leading "/" -- @param func Function to call when the slash command is being used (funcref or methodname) -- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true) function AceConsole:RegisterChatCommand( command, func, persist ) if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand(command, func[, persist ]): 'command' - expected a string]], 2) end if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk local name = "ACECONSOLE_"..strupper(command) local t = type(func) if t == "string" then -- Ace3v: prevent user from using AceConSole as self if self == AceConsole then error([[Usage: RegisterChatCommand(command, func[, persist]): 'self' - use your own 'self']], 2) end SlashCmdList[name] = function(input, editBox) self[func](self, input, editBox) end elseif t == "function" then SlashCmdList[name] = func else error([[Usage: AceConsole:RegisterChatCommand(command, func[, persist ]): 'func' - expected a string or a function]], 2) end _G["SLASH_"..name.."1"] = "/"..strlower(command) AceConsole.commands[command] = name -- non-persisting commands are registered for enabling disabling if not persist then if not AceConsole.weakcommands[self] then AceConsole.weakcommands[self] = {} end AceConsole.weakcommands[self][command] = func end return true end --- Unregister a chatcommand -- @param command Chat command to be unregistered WITHOUT leading "/" function AceConsole:UnregisterChatCommand( command ) local name = AceConsole.commands[command] if name then SlashCmdList[name] = nil _G["SLASH_" .. name .. "1"] = nil AceConsole.commands[command] = nil end end --- Get an iterator over all Chat Commands registered with AceConsole -- @return Iterator (pairs) over all commands function AceConsole:IterateChatCommands() return pairs(AceConsole.commands) end local function nils(n,argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10) if n >= 1 then return nil, nils(n-1,argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10) elseif not argc or argc == 0 then return else return a1, nils(0,argc-1,a2,a3,a4,a5,a6,a7,a8,a9,a10) end end --- Retreive one or more space-separated arguments from a string. -- Treats quoted strings and itemlinks as non-spaced. -- @param str The raw argument string -- @param numargs How many arguments to get (default 1) -- @param startpos Where in the string to start scanning (default 1) -- @return Returns arg1, arg2, ..., nextposition\\ -- Missing arguments will be returned as nils. 'nextposition' is returned as 1e9 at the end of the string. function AceConsole:GetArgs(str, numargs, startpos) numargs = numargs or 1 startpos = max(startpos or 1, 1) local pos=startpos -- find start of new arg pos = strfind(str, "[^ ]", pos) if not pos then -- whoops, end of string return nils(numargs, 1, 1e9) end if numargs<1 then return pos end -- quoted or space separated? find out which pattern to use local delim_or_pipe local ch = strsub(str, pos, pos) if ch=='"' then pos = pos + 1 delim_or_pipe='([|"])' elseif ch=="'" then pos = pos + 1 delim_or_pipe="([|'])" else delim_or_pipe="([| ])" end startpos = pos while true do -- find delimiter or hyperlink local ch,_ pos,_,ch = strfind(str, delim_or_pipe, pos) if not pos then break end if ch=="|" then -- some kind of escape if strsub(str,pos,pos+1)=="|H" then -- It's a |H....|hhyper link!|h pos=strfind(str, "|h", pos+2) -- first |h if not pos then break end pos=strfind(str, "|h", pos+2) -- second |h if not pos then break end elseif strsub(str,pos, pos+1) == "|T" then -- It's a |T....|t texture pos=strfind(str, "|t", pos+2) if not pos then break end end pos=pos+2 -- skip past this escape (last |h if it was a hyperlink) else -- found delimiter, done with this arg return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1) end end -- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink) return strsub(str, startpos), nils(numargs-1, 1, 1e9) end --- embedding and embed handling local mixins = { "Print", "Printf", "RegisterChatCommand", "UnregisterChatCommand", "GetArgs", } -- Embeds AceConsole into the target object making the functions from the mixins list available on target:.. -- @param target target object to embed AceBucket in function AceConsole:Embed( target ) for k, v in pairs( mixins ) do target[v] = self[v] end self.embeds[target] = true return target end function AceConsole:OnEmbedEnable( target ) if AceConsole.weakcommands[target] then for command, func in pairs( AceConsole.weakcommands[target] ) do target:RegisterChatCommand( command, func, false, true ) -- nonpersisting and silent registry end end end function AceConsole:OnEmbedDisable( target ) if AceConsole.weakcommands[target] then for command, func in pairs( AceConsole.weakcommands[target] ) do target:UnregisterChatCommand( command ) -- TODO: this could potentially unregister a command from another application in case of command conflicts. Do we care? end end end for addon in pairs(AceConsole.embeds) do AceConsole:Embed(addon) end