view Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua @ 8:1b2d819b4fa8

Now using the global MailAddonBusy to indicate MailOpener is busy, read comments in Core.lua for more info. Default status is now ?enabled without automatic mail opening? to let first time users look around before MailOpener messes up their heads. A StaticPopupDialog will be added later to ask if they want to auto-enable. When ?enabled without automatic mail opening? is on and you toggle the mail opening checkbox on, mail opening will automatically start.
author Zerotorescue
date Thu, 09 Sep 2010 10:53:19 +0200
parents 823e33465b6e
children
line wrap: on
line source
--- AceConfigCmd-3.0 handles access to an options table through the "command line" interface via the ChatFrames.
-- @class file
-- @name AceConfigCmd-3.0
-- @release $Id: AceConfigCmd-3.0.lua 904 2009-12-13 11:56:37Z nevcairiel $

--[[
AceConfigCmd-3.0

Handles commandline optionstable access

REQUIRES: AceConsole-3.0 for command registration (loaded on demand)

]]

-- TODO: plugin args


local MAJOR, MINOR = "AceConfigCmd-3.0", 12
local AceConfigCmd = LibStub:NewLibrary(MAJOR, MINOR)

if not AceConfigCmd then return end

AceConfigCmd.commands = AceConfigCmd.commands or {}
local commands = AceConfigCmd.commands

local cfgreg = LibStub("AceConfigRegistry-3.0")
local AceConsole -- LoD
local AceConsoleName = "AceConsole-3.0"

-- Lua APIs
local strsub, strsplit, strlower, strmatch, strtrim = string.sub, string.split, string.lower, string.match, string.trim
local format, tonumber, tostring = string.format, tonumber, tostring
local tsort, tinsert = table.sort, table.insert
local select, pairs, next, type = select, pairs, next, type
local error, assert = error, assert

-- WoW APIs
local _G = _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: LibStub, SELECTED_CHAT_FRAME, DEFAULT_CHAT_FRAME


local L = setmetatable({}, {	-- TODO: replace with proper locale
	__index = function(self,k) return k end
})



local function print(msg)
	(SELECTED_CHAT_FRAME or DEFAULT_CHAT_FRAME):AddMessage(msg)
end

-- constants used by getparam() calls below

local handlertypes = {["table"]=true}
local handlermsg = "expected a table"

local functypes = {["function"]=true, ["string"]=true}
local funcmsg = "expected function or member name"


-- pickfirstset() - picks the first non-nil value and returns it

local function pickfirstset(...)	
	for i=1,select("#",...) do
		if select(i,...)~=nil then
			return select(i,...)
		end
	end
end


-- err() - produce real error() regarding malformed options tables etc

local function err(info,inputpos,msg )
	local cmdstr=" "..strsub(info.input, 1, inputpos-1)
	error(MAJOR..": /" ..info[0] ..cmdstr ..": "..(msg or "malformed options table"), 2)
end


-- usererr() - produce chatframe message regarding bad slash syntax etc

local function usererr(info,inputpos,msg )
	local cmdstr=strsub(info.input, 1, inputpos-1);
	print("/" ..info[0] .. " "..cmdstr ..": "..(msg or "malformed options table"))
end


-- callmethod() - call a given named method (e.g. "get", "set") with given arguments

local function callmethod(info, inputpos, tab, methodtype, ...)
	local method = info[methodtype]
	if not method then
		err(info, inputpos, "'"..methodtype.."': not set")
	end

	info.arg = tab.arg
	info.option = tab
	info.type = tab.type

	if type(method)=="function" then
		return method(info, ...)
	elseif type(method)=="string" then
		if type(info.handler[method])~="function" then
			err(info, inputpos, "'"..methodtype.."': '"..method.."' is not a member function of "..tostring(info.handler))
		end
		return info.handler[method](info.handler, info, ...)
	else
		assert(false)	-- type should have already been checked on read
	end
end

-- callfunction() - call a given named function (e.g. "name", "desc") with given arguments

local function callfunction(info, tab, methodtype, ...)
	local method = tab[methodtype]

	info.arg = tab.arg
	info.option = tab
	info.type = tab.type
	
	if type(method)=="function" then
		return method(info, ...)
	else
		assert(false) -- type should have already been checked on read
	end
end

-- do_final() - do the final step (set/execute) along with validation and confirmation

local function do_final(info, inputpos, tab, methodtype, ...)
	if info.validate then 
		local res = callmethod(info,inputpos,tab,"validate",...)
		if type(res)=="string" then
			usererr(info, inputpos, "'"..strsub(info.input, inputpos).."' - "..res)
			return
		end
	end
	-- console ignores .confirm
	
	callmethod(info,inputpos,tab,methodtype, ...)
end


-- getparam() - used by handle() to retreive and store "handler", "get", "set", etc

local function getparam(info, inputpos, tab, depth, paramname, types, errormsg)
	local old,oldat = info[paramname], info[paramname.."_at"]
	local val=tab[paramname]
	if val~=nil then
		if val==false then
			val=nil
		elseif not types[type(val)] then 
			err(info, inputpos, "'" .. paramname.. "' - "..errormsg) 
		end
		info[paramname] = val
		info[paramname.."_at"] = depth
	end
	return old,oldat
end


-- iterateargs(tab) - custom iterator that iterates both t.args and t.plugins.*
local dummytable={}

local function iterateargs(tab)
	if not tab.plugins then 
		return pairs(tab.args) 
	end
	
	local argtabkey,argtab=next(tab.plugins)
	local v
	
	return function(_, k)
		while argtab do
			k,v = next(argtab, k)
			if k then return k,v end
			if argtab==tab.args then
				argtab=nil
			else
				argtabkey,argtab = next(tab.plugins, argtabkey)
				if not argtabkey then
					argtab=tab.args
				end
			end
		end
	end
end

local function checkhidden(info, inputpos, tab)
	if tab.cmdHidden~=nil then
		return tab.cmdHidden
	end
	local hidden = tab.hidden
	if type(hidden) == "function" or type(hidden) == "string" then
		info.hidden = hidden
		hidden = callmethod(info, inputpos, tab, 'hidden')
		info.hidden = nil
	end
	return hidden
end

local function showhelp(info, inputpos, tab, depth, noHead)
	if not noHead then
		print("|cff33ff99"..info.appName.."|r: Arguments to |cffffff78/"..info[0].."|r "..strsub(info.input,1,inputpos-1)..":")
	end
	
	local sortTbl = {}	-- [1..n]=name
	local refTbl = {}   -- [name]=tableref
	
	for k,v in iterateargs(tab) do
		if not refTbl[k] then	-- a plugin overriding something in .args
			tinsert(sortTbl, k)
			refTbl[k] = v
		end
	end
	
	tsort(sortTbl, function(one, two) 
		local o1 = refTbl[one].order or 100
		local o2 = refTbl[two].order or 100
		if type(o1) == "function" or type(o1) == "string" then
			info.order = o1
			info[#info+1] = one
			o1 = callmethod(info, inputpos, refTbl[one], "order")
			info[#info] = nil
			info.order = nil
		end
		if type(o2) == "function" or type(o1) == "string" then
			info.order = o2
			info[#info+1] = two
			o2 = callmethod(info, inputpos, refTbl[two], "order")
			info[#info] = nil
			info.order = nil
		end
		if o1<0 and o2<0 then return o1<o2 end
		if o2<0 then return true end
		if o1<0 then return false end
		if o1==o2 then return tostring(one)<tostring(two) end   -- compare names
		return o1<o2
	end)
	
	for i = 1, #sortTbl do
		local k = sortTbl[i]
		local v = refTbl[k]
		if not checkhidden(info, inputpos, v) then
			if v.type ~= "description" and v.type ~= "header" then
				-- recursively show all inline groups
				local name, desc = v.name, v.desc
				if type(name) == "function" then
					name = callfunction(info, v, 'name')
				end
				if type(desc) == "function" then
					desc = callfunction(info, v, 'desc')
				end
				if v.type == "group" and pickfirstset(v.cmdInline, v.inline, false) then
					print("  "..(desc or name)..":")
					local oldhandler,oldhandler_at = getparam(info, inputpos, v, depth, "handler", handlertypes, handlermsg)
					showhelp(info, inputpos, v, depth, true)
					info.handler,info.handler_at = oldhandler,oldhandler_at
				else
					local key = k:gsub(" ", "_")
					print("  |cffffff78"..key.."|r - "..(desc or name or ""))
				end
			end
		end
	end
end


local function keybindingValidateFunc(text)
	if text == nil or text == "NONE" then
		return nil
	end
	text = text:upper()
	local shift, ctrl, alt
	local modifier
	while true do
		if text == "-" then
			break
		end
		modifier, text = strsplit('-', text, 2)
		if text then
			if modifier ~= "SHIFT" and modifier ~= "CTRL" and modifier ~= "ALT" then
				return false
			end
			if modifier == "SHIFT" then
				if shift then
					return false
				end
				shift = true
			end
			if modifier == "CTRL" then
				if ctrl then
					return false
				end
				ctrl = true
			end
			if modifier == "ALT" then
				if alt then
					return false
				end
				alt = true
			end
		else
			text = modifier
			break
		end
	end
	if text == "" then
		return false
	end
	if not text:find("^F%d+$") and text ~= "CAPSLOCK" and text:len() ~= 1 and (text:byte() < 128 or text:len() > 4) and not _G["KEY_" .. text] then
		return false
	end
	local s = text
	if shift then
		s = "SHIFT-" .. s
	end
	if ctrl then
		s = "CTRL-" .. s
	end
	if alt then
		s = "ALT-" .. s
	end
	return s
end

-- handle() - selfrecursing function that processes input->optiontable 
-- - depth - starts at 0
-- - retfalse - return false rather than produce error if a match is not found (used by inlined groups)

local function handle(info, inputpos, tab, depth, retfalse)

	if not(type(tab)=="table" and type(tab.type)=="string") then err(info,inputpos) end

	-------------------------------------------------------------------
	-- Grab hold of handler,set,get,func,etc if set (and remember old ones)
	-- Note that we do NOT validate if method names are correct at this stage,
	-- the handler may change before they're actually used!

	local oldhandler,oldhandler_at = getparam(info,inputpos,tab,depth,"handler",handlertypes,handlermsg)
	local oldset,oldset_at = getparam(info,inputpos,tab,depth,"set",functypes,funcmsg)
	local oldget,oldget_at = getparam(info,inputpos,tab,depth,"get",functypes,funcmsg)
	local oldfunc,oldfunc_at = getparam(info,inputpos,tab,depth,"func",functypes,funcmsg)
	local oldvalidate,oldvalidate_at = getparam(info,inputpos,tab,depth,"validate",functypes,funcmsg)
	--local oldconfirm,oldconfirm_at = getparam(info,inputpos,tab,depth,"confirm",functypes,funcmsg)
	
	-------------------------------------------------------------------
	-- Act according to .type of this table
		
	if tab.type=="group" then
		------------ group --------------------------------------------
		
		if type(tab.args)~="table" then err(info, inputpos) end
		if tab.plugins and type(tab.plugins)~="table" then err(info,inputpos) end
		
		-- grab next arg from input
		local _,nextpos,arg = (info.input):find(" *([^ ]+) *", inputpos)
		if not arg then
			showhelp(info, inputpos, tab, depth)
			return
		end
		nextpos=nextpos+1
		
		-- loop .args and try to find a key with a matching name
		for k,v in iterateargs(tab) do
			if not(type(k)=="string" and type(v)=="table" and type(v.type)=="string") then err(info,inputpos, "options table child '"..tostring(k).."' is malformed") end
			
			-- is this child an inline group? if so, traverse into it
			if v.type=="group" and pickfirstset(v.cmdInline, v.inline, false) then
				info[depth+1] = k
				if handle(info, inputpos, v, depth+1, true)==false then
					info[depth+1] = nil
					-- wasn't found in there, but that's ok, we just keep looking down here
				else
					return	-- done, name was found in inline group
				end
			-- matching name and not a inline group
			elseif strlower(arg)==strlower(k:gsub(" ", "_")) then
				info[depth+1] = k
				return handle(info,nextpos,v,depth+1)
			end
		end
			
		-- no match 
		if retfalse then
			-- restore old infotable members and return false to indicate failure
			info.handler,info.handler_at = oldhandler,oldhandler_at
			info.set,info.set_at = oldset,oldset_at
			info.get,info.get_at = oldget,oldget_at
			info.func,info.func_at = oldfunc,oldfunc_at
			info.validate,info.validate_at = oldvalidate,oldvalidate_at
			--info.confirm,info.confirm_at = oldconfirm,oldconfirm_at
			return false
		end
		
		-- couldn't find the command, display error
		usererr(info, inputpos, "'"..arg.."' - " .. L["unknown argument"])
		return
	end
	
	local str = strsub(info.input,inputpos);
	
	if tab.type=="execute" then
		------------ execute --------------------------------------------
		do_final(info, inputpos, tab, "func")
		

	
	elseif tab.type=="input" then
		------------ input --------------------------------------------
		
		local res = true
		if tab.pattern then
			if not(type(tab.pattern)=="string") then err(info, inputpos, "'pattern' - expected a string") end
			if not strmatch(str, tab.pattern) then
				usererr(info, inputpos, "'"..str.."' - " .. L["invalid input"])
				return
			end
		end
		
		do_final(info, inputpos, tab, "set", str)
		

	
	elseif tab.type=="toggle" then
		------------ toggle --------------------------------------------
		local b
		local str = strtrim(strlower(str))
		if str=="" then
			b = callmethod(info, inputpos, tab, "get")

			if tab.tristate then
				--cycle in true, nil, false order
				if b then
					b = nil
				elseif b == nil then
					b = false
				else
					b = true
				end
			else
				b = not b
			end
			
		elseif str==L["on"] then
			b = true
		elseif str==L["off"] then
			b = false
		elseif tab.tristate and str==L["default"] then
			b = nil
		else
			if tab.tristate then
				usererr(info, inputpos, format(L["'%s' - expected 'on', 'off' or 'default', or no argument to toggle."], str))
			else
				usererr(info, inputpos, format(L["'%s' - expected 'on' or 'off', or no argument to toggle."], str))
			end
			return
		end
		
		do_final(info, inputpos, tab, "set", b)
		

	elseif tab.type=="range" then
		------------ range --------------------------------------------
		local val = tonumber(str)
		if not val then
			usererr(info, inputpos, "'"..str.."' - "..L["expected number"])
			return
		end
		if type(info.step)=="number" then
			val = val- (val % info.step)
		end
		if type(info.min)=="number" and val<info.min then
			usererr(info, inputpos, val.." - "..format(L["must be equal to or higher than %s"], tostring(info.min)) )
			return
		end
		if type(info.max)=="number" and val>info.max then
			usererr(info, inputpos, val.." - "..format(L["must be equal to or lower than %s"], tostring(info.max)) )
			return
		end
		
		do_final(info, inputpos, tab, "set", val)

	
	elseif tab.type=="select" then
		------------ select ------------------------------------
		local str = strtrim(strlower(str))
		
		local values = tab.values
		if type(values) == "function" or type(values) == "string" then
			info.values = values
			values = callmethod(info, inputpos, tab, "values")
			info.values = nil
		end
		
		if str == "" then
			local b = callmethod(info, inputpos, tab, "get")
			local fmt = "|cffffff78- [%s]|r %s"
			local fmt_sel = "|cffffff78- [%s]|r %s |cffff0000*|r"
			print(L["Options for |cffffff78"..info[#info].."|r:"])
			for k, v in pairs(values) do
				if b == k then
					print(fmt_sel:format(k, v))
				else
					print(fmt:format(k, v))
				end
			end
			return
		end

		local ok
		for k,v in pairs(values) do 
			if strlower(k)==str then
				str = k	-- overwrite with key (in case of case mismatches)
				ok = true
				break
			end
		end
		if not ok then
			usererr(info, inputpos, "'"..str.."' - "..L["unknown selection"])
			return
		end
		
		do_final(info, inputpos, tab, "set", str)
		
	elseif tab.type=="multiselect" then
		------------ multiselect -------------------------------------------
		local str = strtrim(strlower(str))
		
		local values = tab.values
		if type(values) == "function" or type(values) == "string" then
			info.values = values
			values = callmethod(info, inputpos, tab, "values")
			info.values = nil
		end
		
		if str == "" then
			local fmt = "|cffffff78- [%s]|r %s"
			local fmt_sel = "|cffffff78- [%s]|r %s |cffff0000*|r"
			print(L["Options for |cffffff78"..info[#info].."|r (multiple possible):"])
			for k, v in pairs(values) do
				if callmethod(info, inputpos, tab, "get", k) then
					print(fmt_sel:format(k, v))
				else
					print(fmt:format(k, v))
				end
			end
			return
		end
		
		--build a table of the selections, checking that they exist
		--parse for =on =off =default in the process
		--table will be key = true for options that should toggle, key = [on|off|default] for options to be set
		local sels = {}
		for v in str:gmatch("[^ ]+") do
			--parse option=on etc
			local opt, val = v:match('(.+)=(.+)')
			--get option if toggling
			if not opt then 
				opt = v 
			end
			
			--check that the opt is valid
			local ok
			for k,v in pairs(values) do 
				if strlower(k)==opt then
					opt = k	-- overwrite with key (in case of case mismatches)
					ok = true
					break
				end
			end
			
			if not ok then
				usererr(info, inputpos, "'"..opt.."' - "..L["unknown selection"])
				return
			end
			
			--check that if val was supplied it is valid
			if val then
				if val == L["on"] or val == L["off"] or (tab.tristate and val == L["default"]) then
					--val is valid insert it
					sels[opt] = val
				else
					if tab.tristate then
						usererr(info, inputpos, format(L["'%s' '%s' - expected 'on', 'off' or 'default', or no argument to toggle."], v, val))
					else
						usererr(info, inputpos, format(L["'%s' '%s' - expected 'on' or 'off', or no argument to toggle."], v, val))
					end
					return
				end
			else
				-- no val supplied, toggle
				sels[opt] = true
			end
		end
		
		for opt, val in pairs(sels) do
			local newval
			
			if (val == true) then
				--toggle the option
				local b = callmethod(info, inputpos, tab, "get", opt)
				
				if tab.tristate then
					--cycle in true, nil, false order
					if b then
						b = nil
					elseif b == nil then
						b = false
					else
						b = true
					end
				else
					b = not b
				end
				newval = b
			else
				--set the option as specified
				if val==L["on"] then
					newval = true
				elseif val==L["off"] then
					newval = false
				elseif val==L["default"] then
					newval = nil
				end
			end
			
			do_final(info, inputpos, tab, "set", opt, newval)
		end
					
		
	elseif tab.type=="color" then
		------------ color --------------------------------------------
		local str = strtrim(strlower(str))
		if str == "" then
			--TODO: Show current value
			return
		end
		
		local r, g, b, a
		
		if tab.hasAlpha then
			if str:len() == 8 and str:find("^%x*$")  then
				--parse a hex string
				r,g,b,a = tonumber(str:sub(1, 2), 16) / 255, tonumber(str:sub(3, 4), 16) / 255, tonumber(str:sub(5, 6), 16) / 255, tonumber(str:sub(7, 8), 16) / 255
			else
				--parse seperate values
				r,g,b,a = str:match("^([%d%.]+) ([%d%.]+) ([%d%.]+) ([%d%.]+)$")
				r,g,b,a = tonumber(r), tonumber(g), tonumber(b), tonumber(a)
			end
			if not (r and g and b and a) then
				usererr(info, inputpos, format(L["'%s' - expected 'RRGGBBAA' or 'r g b a'."], str))
				return
			end
			
			if r >= 0.0 and r <= 1.0 and g >= 0.0 and g <= 1.0 and b >= 0.0 and b <= 1.0 and a >= 0.0 and a <= 1.0 then
				--values are valid
			elseif r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 and a >= 0 and a <= 255 then
				--values are valid 0..255, convert to 0..1
				r = r / 255
				g = g / 255
				b = b / 255
				a = a / 255
			else
				--values are invalid
				usererr(info, inputpos, format(L["'%s' - values must all be either in the range 0..1 or 0..255."], str))
			end
		else
			a = 1.0
			if str:len() == 6 and str:find("^%x*$") then
				--parse a hex string
				r,g,b = tonumber(str:sub(1, 2), 16) / 255, tonumber(str:sub(3, 4), 16) / 255, tonumber(str:sub(5, 6), 16) / 255
			else
				--parse seperate values
				r,g,b = str:match("^([%d%.]+) ([%d%.]+) ([%d%.]+)$")
				r,g,b = tonumber(r), tonumber(g), tonumber(b)
			end
			if not (r and g and b) then
				usererr(info, inputpos, format(L["'%s' - expected 'RRGGBB' or 'r g b'."], str))
				return
			end
			if r >= 0.0 and r <= 1.0 and g >= 0.0 and g <= 1.0 and b >= 0.0 and b <= 1.0 then
				--values are valid
			elseif r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 then
				--values are valid 0..255, convert to 0..1
				r = r / 255
				g = g / 255
				b = b / 255
			else
				--values are invalid
				usererr(info, inputpos, format(L["'%s' - values must all be either in the range 0-1 or 0-255."], str))
			end
		end
		
		do_final(info, inputpos, tab, "set", r,g,b,a)

	elseif tab.type=="keybinding" then
		------------ keybinding --------------------------------------------
		local str = strtrim(strlower(str))
		if str == "" then
			--TODO: Show current value
			return
		end
		local value = keybindingValidateFunc(str:upper())
		if value == false then
			usererr(info, inputpos, format(L["'%s' - Invalid Keybinding."], str))
			return
		end

		do_final(info, inputpos, tab, "set", value)

	elseif tab.type=="description" then
		------------ description --------------------
		-- ignore description, GUI config only
	else
		err(info, inputpos, "unknown options table item type '"..tostring(tab.type).."'")
	end
end

--- Handle the chat command.
-- This is usually called from a chat command handler to parse the command input as operations on an aceoptions table.\\
-- AceConfigCmd uses this function internally when a slash command is registered with `:CreateChatCommand`
-- @param slashcmd The slash command WITHOUT leading slash (only used for error output)
-- @param appName The application name as given to `:RegisterOptionsTable()`
-- @param input The commandline input (as given by the WoW handler, i.e. without the command itself)
-- @usage
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceConsole-3.0")
-- -- Use AceConsole-3.0 to register a Chat Command
-- MyAddon:RegisterChatCommand("mychat", "ChatCommand")
-- 
-- -- Show the GUI if no input is supplied, otherwise handle the chat input.
-- function MyAddon:ChatCommand(input)
--   -- Assuming "MyOptions" is the appName of a valid options table
--   if not input or input:trim() == "" then
--     LibStub("AceConfigDialog-3.0"):Open("MyOptions")
--   else
--     LibStub("AceConfigCmd-3.0").HandleCommand(MyAddon, "mychat", "MyOptions", input)
--   end
-- end
function AceConfigCmd:HandleCommand(slashcmd, appName, input)

	local optgetter = cfgreg:GetOptionsTable(appName)
	if not optgetter then
		error([[Usage: HandleCommand("slashcmd", "appName", "input"): 'appName' - no options table "]]..tostring(appName)..[[" has been registered]], 2)
	end
	local options = assert( optgetter("cmd", MAJOR) )
	
	local info = {   -- Don't try to recycle this, it gets handed off to callbacks and whatnot
		[0] = slashcmd,
		appName = appName,
		options = options,
		input = input,
		self = self,
		handler = self,
		uiType = "cmd",
		uiName = MAJOR,
	}
	
	handle(info, 1, options, 0)  -- (info, inputpos, table, depth)
end

--- Utility function to create a slash command handler.
-- Also registers tab completion with AceTab
-- @param slashcmd The slash command WITHOUT leading slash (only used for error output)
-- @param appName The application name as given to `:RegisterOptionsTable()`
function AceConfigCmd:CreateChatCommand(slashcmd, appName)
	if not AceConsole then
		AceConsole = LibStub(AceConsoleName)
	end
	if AceConsole.RegisterChatCommand(self, slashcmd, function(input)
				AceConfigCmd.HandleCommand(self, slashcmd, appName, input)	-- upgradable
		end,
	true) then -- succesfully registered so lets get the command -> app table in
		commands[slashcmd] = appName
	end
end

--- Utility function that returns the options table that belongs to a slashcommand.
-- Designed to be used for the AceTab interface.
-- @param slashcmd The slash command WITHOUT leading slash (only used for error output)
-- @return The options table associated with the slash command (or nil if the slash command was not registered)
function AceConfigCmd:GetChatCommandOptions(slashcmd)
	return commands[slashcmd]
end