local c=LibStub("\065\099\101\067\111\110\102\105\103\082\101\103\105\115\116\114\121-\051\046\048")local z,Y="\065\099\101\067\111\110\102\105\103\067\109\100-\051\046\048",14 local k=LibStub:NewLibrary(z,Y)if not k then return end k.commands=k.commands or{}local O=k.commands local u local N="\065\099\101\067\111\110\115\111\108\101-\051\046\048"local P,I,j,n,M=string.sub,string.split,string.lower,string.match,string.trim local S,X,F=string.format,tonumber,tostring local m,y=table.sort,table.insert local x,w,L,e=select,pairs,next,type local q,a=error,assert local v=_G local p=setmetatable({},{__index=function(c,z)return z end})local function Q(c)(SELECTED_CHAT_FRAME or DEFAULT_CHAT_FRAME):AddMessage(c)end local f={table=true}local t="\101\120\112\101\099\116\101\100 \097 \116\097\098\108\101"local K={["\102\117\110\099\116\105\111\110"]=true;string=true}local C="\101\120\112\101\099\116\101\100 \102\117\110\099\116\105\111\110 \111\114 \109\101\109\098\101\114 \110\097\109\101"local function W(...)for c=1,x("\035",...),1 do if x(c,...)~=nil then return x(c,...)end end end local function E(c,Y,k)local O=" "..P(c.input,1,Y-1)q(z..("\058 \047"..(c[0]..(O..("\058 "..(k or"\109\097\108\102\111\114\109\101\100 \111\112\116\105\111\110\115 \116\097\098\108\101"))))),2)end local function s(c,z,Y)local k=P(c.input,1,z-1)Q("\047"..(c[0]..(" "..(k..("\058 "..(Y or"\109\097\108\102\111\114\109\101\100 \111\112\116\105\111\110\115 \116\097\098\108\101"))))))end local function V(c,z,Y,k,...)local O=c[k]if not O then E(c,z,"\'"..(k.."\'\058 \110\111\116 \115\101\116"))end c.arg=Y.arg c.option=Y c.type=Y.type if e(O)=="\102\117\110\099\116\105\111\110"then return O(c,...)elseif e(O)=="\115\116\114\105\110\103"then if e(c.handler[O])~="\102\117\110\099\116\105\111\110"then E(c,z,"\'"..(k..("\'\058 \'"..(O..("\' \105\115 \110\111\116 \097 \109\101\109\098\101\114 \102\117\110\099\116\105\111\110 \111\102 "..F(c.handler))))))end return c.handler[O](c.handler,c,...)else a(false)end end local function i(c,z,Y,...)local k=z[Y]c.arg=z.arg c.option=z c.type=z.type if e(k)=="\102\117\110\099\116\105\111\110"then return k(c,...)else a(false)end end local function B(c,z,Y,k,...)if c.validate then local k=V(c,z,Y,"\118\097\108\105\100\097\116\101",...)if e(k)=="\115\116\114\105\110\103"then s(c,z,"\'"..(P(c.input,z)..("\' - "..k)))return end end V(c,z,Y,k,...)end local function D(c,z,Y,k,O,u,N)local P,I=c[O],c[O.."\095\097\116"]local j=Y[O]if j~=nil then if j==false then j=nil elseif not u[e(j)]then E(c,z,"\'"..(O..("\' - "..N)))end c[O]=j c[O.."\095\097\116"]=k end return P,I end local R={}local function d(c)if not c.plugins then return w(c.args)end local z,Y=L(c.plugins)local k return function(O,u)while Y do u,k=L(Y,u)if u then return u,k end if Y==c.args then Y=nil else z,Y=L(c.plugins,z)if not z then Y=c.args end end end end end local function A(c,z,Y)if Y.cmdHidden~=nil then return Y.cmdHidden end local k=Y.hidden if e(k)=="\102\117\110\099\116\105\111\110"or e(k)=="\115\116\114\105\110\103"then c.hidden=k k=V(c,z,Y,"\104\105\100\100\101\110")c.hidden=nil end return k end local function Z(c,z,Y,k,O)if not O then Q("\124\099\102\102\051\051\102\102\057\057"..(c.appName..("\124\114\058 \065\114\103\117\109\101\110\116\115 \116\111 \124\099\102\102\102\102\102\102\055\056\047"..(c[0]..("\124\114 "..(P(c.input,1,z-1).."\058"))))))end local u={}local N={}for c,z in d(Y)do if not N[c]then y(u,c)N[c]=z end end m(u,function(Y,k)local O=N[Y].order or 100 local u=N[k].order or 100 if e(O)=="\102\117\110\099\116\105\111\110"or e(O)=="\115\116\114\105\110\103"then c.order=O c[#c+1]=Y O=V(c,z,N[Y],"\111\114\100\101\114")c[#c]=nil c.order=nil end if e(u)=="\102\117\110\099\116\105\111\110"or e(O)=="\115\116\114\105\110\103"then c.order=u c[#c+1]=k u=V(c,z,N[k],"\111\114\100\101\114")c[#c]=nil c.order=nil end if O<0 and u<0 then return O<u end if u<0 then return true end if O<0 then return false end if O==u then return F(Y)<F(k)end return O<u end)for Y=1,#u,1 do local O=u[Y]local P=N[O]if not A(c,z,P)then if P.type~="\100\101\115\099\114\105\112\116\105\111\110"and P.type~="\104\101\097\100\101\114"then local Y,u=P.name,P.desc if e(Y)=="\102\117\110\099\116\105\111\110"then Y=i(c,P,"\110\097\109\101")end if e(u)=="\102\117\110\099\116\105\111\110"then u=i(c,P,"\100\101\115\099")end if P.type=="\103\114\111\117\112"and W(P.cmdInline,P.inline,false)then Q("  "..((u or Y).."\058"))local O,N=D(c,z,P,k,"\104\097\110\100\108\101\114",f,t)Z(c,z,P,k,true)c.handler,c.handler_at=O,N else local c=O:gsub(" ","\095")Q("  \124\099\102\102\102\102\102\102\055\056"..(c..("\124\114 - "..(u or Y or""))))end end end end end local function b(c)if c==nil or c=="\078\079\078\069"then return nil end c=c:upper()local z,Y,k local O while true do if c=="-"then break end O,c=I("-",c,2)if c then if O~="\083\072\073\070\084"and(O~="\067\084\082\076"and O~="\065\076\084")then return false end if O=="\083\072\073\070\084"then if z then return false end z=true end if O=="\067\084\082\076"then if Y then return false end Y=true end if O=="\065\076\084"then if k then return false end k=true end else c=O break end end if c==""then return false end if not c:find("\094\070\037\100\043\036")and(c~="\067\065\080\083\076\079\067\075"and(c:len()~=1 and((c:byte()<128 or c:len()>4)and not v["\075\069\089\095"..c])))then return false end local u=c if z then u="\083\072\073\070\084-"..u end if Y then u="\067\084\082\076-"..u end if k then u="\065\076\084-"..u end return u end local function l(c,z,Y,k,O)if not(e(Y)=="\116\097\098\108\101"and e(Y.type)=="\115\116\114\105\110\103")then E(c,z)end local u,N=D(c,z,Y,k,"\104\097\110\100\108\101\114",f,t)local I,m=D(c,z,Y,k,"\115\101\116",K,C)local y,x=D(c,z,Y,k,"\103\101\116",K,C)local L,q=D(c,z,Y,k,"\102\117\110\099",K,C)local a,v=D(c,z,Y,k,"\118\097\108\105\100\097\116\101",K,C)if Y.type=="\103\114\111\117\112"then if e(Y.args)~="\116\097\098\108\101"then E(c,z)end if Y.plugins and e(Y.plugins)~="\116\097\098\108\101"then E(c,z)end local P,n,M=c.input:find(" \042\040\091\094 \093\043\041 \042",z)if not M then Z(c,z,Y,k)return end n=n+1 for Y,O in d(Y)do if not(e(Y)=="\115\116\114\105\110\103"and(e(O)=="\116\097\098\108\101"and e(O.type)=="\115\116\114\105\110\103"))then E(c,z,"\111\112\116\105\111\110\115 \116\097\098\108\101 \099\104\105\108\100 \'"..(F(Y).."\' \105\115 \109\097\108\102\111\114\109\101\100"))end if O.type=="\103\114\111\117\112"and W(O.cmdInline,O.inline,false)then c[k+1]=Y if l(c,z,O,k+1,true)==false then c[k+1]=nil else return end elseif j(M)==j(Y:gsub(" ","\095"))then c[k+1]=Y return l(c,n,O,k+1)end end if O then c.handler,c.handler_at=u,N c.set,c.set_at=I,m c.get,c.get_at=y,x c.func,c.func_at=L,q c.validate,c.validate_at=a,v return false end s(c,z,"\'"..(M..("\' - "..p["\117\110\107\110\111\119\110 \097\114\103\117\109\101\110\116"])))return end local i=P(c.input,z)if Y.type=="\101\120\101\099\117\116\101"then B(c,z,Y,"\102\117\110\099")elseif Y.type=="\105\110\112\117\116"then local k=true if Y.pattern then if e(Y.pattern)~="\115\116\114\105\110\103"then E(c,z,"\'\112\097\116\116\101\114\110\' - \101\120\112\101\099\116\101\100 \097 \115\116\114\105\110\103")end if not n(i,Y.pattern)then s(c,z,"\'"..(i..("\' - "..p["\105\110\118\097\108\105\100 \105\110\112\117\116"])))return end end B(c,z,Y,"\115\101\116",i)elseif Y.type=="\116\111\103\103\108\101"then local k local O=M(j(i))if O==""then k=V(c,z,Y,"\103\101\116")if Y.tristate then if k then k=nil elseif k==nil then k=false else k=true end else k=not k end elseif O==p.on then k=true elseif O==p.off then k=false elseif Y.tristate and O==p.default then k=nil else if Y.tristate then s(c,z,S(p["\'\037\115\' - \101\120\112\101\099\116\101\100 \'\111\110\'\044 \'\111\102\102\' \111\114 \'\100\101\102\097\117\108\116\'\044 \111\114 \110\111 \097\114\103\117\109\101\110\116 \116\111 \116\111\103\103\108\101\046"],O))else s(c,z,S(p["\'\037\115\' - \101\120\112\101\099\116\101\100 \'\111\110\' \111\114 \'\111\102\102\'\044 \111\114 \110\111 \097\114\103\117\109\101\110\116 \116\111 \116\111\103\103\108\101\046"],O))end return end B(c,z,Y,"\115\101\116",k)elseif Y.type=="\114\097\110\103\101"then local k=X(i)if not k then s(c,z,"\'"..(i..("\' - "..p["\101\120\112\101\099\116\101\100 \110\117\109\098\101\114"])))return end if e(c.step)=="\110\117\109\098\101\114"then k=k-k%c.step end if e(c.min)=="\110\117\109\098\101\114"and k<c.min then s(c,z,k..(" - "..S(p["\109\117\115\116 \098\101 \101\113\117\097\108 \116\111 \111\114 \104\105\103\104\101\114 \116\104\097\110 \037\115"],F(c.min))))return end if e(c.max)=="\110\117\109\098\101\114"and k>c.max then s(c,z,k..(" - "..S(p["\109\117\115\116 \098\101 \101\113\117\097\108 \116\111 \111\114 \108\111\119\101\114 \116\104\097\110 \037\115"],F(c.max))))return end B(c,z,Y,"\115\101\116",k)elseif Y.type=="\115\101\108\101\099\116"then local k=M(j(i))local O=Y.values if e(O)=="\102\117\110\099\116\105\111\110"or e(O)=="\115\116\114\105\110\103"then c.values=O O=V(c,z,Y,"\118\097\108\117\101\115")c.values=nil end if k==""then local k=V(c,z,Y,"\103\101\116")local u="\124\099\102\102\102\102\102\102\055\056- \091\037\115\093\124\114 \037\115"local N="\124\099\102\102\102\102\102\102\055\056- \091\037\115\093\124\114 \037\115 \124\099\102\102\102\102\048\048\048\048\042\124\114"Q(p["\079\112\116\105\111\110\115 \102\111\114 \124\099\102\102\102\102\102\102\055\056"..(c[#c].."\124\114\058")])for c,z in w(O)do if k==c then Q(N:format(c,z))else Q(u:format(c,z))end end return end local u for c,z in w(O)do if j(c)==k then k=c u=true break end end if not u then s(c,z,"\'"..(k..("\' - "..p["\117\110\107\110\111\119\110 \115\101\108\101\099\116\105\111\110"])))return end B(c,z,Y,"\115\101\116",k)elseif Y.type=="\109\117\108\116\105\115\101\108\101\099\116"then local k=M(j(i))local O=Y.values if e(O)=="\102\117\110\099\116\105\111\110"or e(O)=="\115\116\114\105\110\103"then c.values=O O=V(c,z,Y,"\118\097\108\117\101\115")c.values=nil end if k==""then local k="\124\099\102\102\102\102\102\102\055\056- \091\037\115\093\124\114 \037\115"local u="\124\099\102\102\102\102\102\102\055\056- \091\037\115\093\124\114 \037\115 \124\099\102\102\102\102\048\048\048\048\042\124\114"Q(p["\079\112\116\105\111\110\115 \102\111\114 \124\099\102\102\102\102\102\102\055\056"..(c[#c].."\124\114 \040\109\117\108\116\105\112\108\101 \112\111\115\115\105\098\108\101\041\058")])for O,N in w(O)do if V(c,z,Y,"\103\101\116",O)then Q(u:format(O,N))else Q(k:format(O,N))end end return end local u={}for k in k:gmatch("\091\094 \093\043")do local N,P=k:match("\040\046\043\041\061\040\046\043\041")if not N then N=k end local I for c in w(O)do if j(c)==N then N=c I=true break end end if not I then s(c,z,"\'"..(N..("\' - "..p["\117\110\107\110\111\119\110 \115\101\108\101\099\116\105\111\110"])))return end if P then if P==p.on or P==p.off or Y.tristate and P==p.default then u[N]=P else if Y.tristate then s(c,z,S(p["\'\037\115\' \'\037\115\' - \101\120\112\101\099\116\101\100 \'\111\110\'\044 \'\111\102\102\' \111\114 \'\100\101\102\097\117\108\116\'\044 \111\114 \110\111 \097\114\103\117\109\101\110\116 \116\111 \116\111\103\103\108\101\046"],k,P))else s(c,z,S(p["\'\037\115\' \'\037\115\' - \101\120\112\101\099\116\101\100 \'\111\110\' \111\114 \'\111\102\102\'\044 \111\114 \110\111 \097\114\103\117\109\101\110\116 \116\111 \116\111\103\103\108\101\046"],k,P))end return end else u[N]=true end end for k,O in w(u)do local u if O==true then local O=V(c,z,Y,"\103\101\116",k)if Y.tristate then if O then O=nil elseif O==nil then O=false else O=true end else O=not O end u=O else if O==p.on then u=true elseif O==p.off then u=false elseif O==p.default then u=nil end end B(c,z,Y,"\115\101\116",k,u)end elseif Y.type=="\099\111\108\111\114"then local k=M(j(i))if k==""then return end local O,u,N,P local I=Y.hasAlpha if e(I)=="\102\117\110\099\116\105\111\110"or e(I)=="\115\116\114\105\110\103"then c.hasAlpha=I I=V(c,z,Y,"\104\097\115\065\108\112\104\097")c.hasAlpha=nil end if I then if k:len()==8 and k:find("\094\037\120\042\036")then O,u,N,P=X(k:sub(1,2),16)/255,X(k:sub(3,4),16)/255,X(k:sub(5,6),16)/255,X(k:sub(7,8),16)/255 else O,u,N,P=k:match("\094\040\091\037\100\037\046\093\043\041 \040\091\037\100\037\046\093\043\041 \040\091\037\100\037\046\093\043\041 \040\091\037\100\037\046\093\043\041\036")O,u,N,P=X(O),X(u),X(N),X(P)end if not(O and(u and(N and P)))then s(c,z,S(p["\'\037\115\' - \101\120\112\101\099\116\101\100 \'\082\082\071\071\066\066\065\065\' \111\114 \'\114 \103 \098 \097\'\046"],k))return end if O>=.0 and(O<=1.0 and(u>=.0 and(u<=1.0 and(N>=.0 and(N<=1.0 and(P>=.0 and P<=1.0))))))then elseif O>=0 and(O<=255 and(u>=0 and(u<=255 and(N>=0 and(N<=255 and(P>=0 and P<=255))))))then O=O/255 u=u/255 N=N/255 P=P/255 else s(c,z,S(p["\'\037\115\' - \118\097\108\117\101\115 \109\117\115\116 \097\108\108 \098\101 \101\105\116\104\101\114 \105\110 \116\104\101 \114\097\110\103\101 \048\046\046\049 \111\114 \048\046\046\050\053\053\046"],k))end else P=1.0 if k:len()==6 and k:find("\094\037\120\042\036")then O,u,N=X(k:sub(1,2),16)/255,X(k:sub(3,4),16)/255,X(k:sub(5,6),16)/255 else O,u,N=k:match("\094\040\091\037\100\037\046\093\043\041 \040\091\037\100\037\046\093\043\041 \040\091\037\100\037\046\093\043\041\036")O,u,N=X(O),X(u),X(N)end if not(O and(u and N))then s(c,z,S(p["\'\037\115\' - \101\120\112\101\099\116\101\100 \'\082\082\071\071\066\066\' \111\114 \'\114 \103 \098\'\046"],k))return end if O>=.0 and(O<=1.0 and(u>=.0 and(u<=1.0 and(N>=.0 and N<=1.0))))then elseif O>=0 and(O<=255 and(u>=0 and(u<=255 and(N>=0 and N<=255))))then O=O/255 u=u/255 N=N/255 else s(c,z,S(p["\'\037\115\' - \118\097\108\117\101\115 \109\117\115\116 \097\108\108 \098\101 \101\105\116\104\101\114 \105\110 \116\104\101 \114\097\110\103\101 \048-\049 \111\114 \048-\050\053\053\046"],k))end end B(c,z,Y,"\115\101\116",O,u,N,P)elseif Y.type=="\107\101\121\098\105\110\100\105\110\103"then local k=M(j(i))if k==""then return end local O=b(k:upper())if O==false then s(c,z,S(p["\'\037\115\' - \073\110\118\097\108\105\100 \075\101\121\098\105\110\100\105\110\103\046"],k))return end B(c,z,Y,"\115\101\116",O)elseif Y.type=="\100\101\115\099\114\105\112\116\105\111\110"then else E(c,z,"\117\110\107\110\111\119\110 \111\112\116\105\111\110\115 \116\097\098\108\101 \105\116\101\109 \116\121\112\101 \'"..(F(Y.type).."\'"))end end function k.HandleCommand(u,Y,k,O)local N=c:GetOptionsTable(k)if not N then q("\085\115\097\103\101\058 \072\097\110\100\108\101\067\111\109\109\097\110\100\040\"\115\108\097\115\104\099\109\100\"\044 \"\097\112\112\078\097\109\101\"\044 \"\105\110\112\117\116\"\041\058 \'\097\112\112\078\097\109\101\' - \110\111 \111\112\116\105\111\110\115 \116\097\098\108\101 \""..(F(k).."\" \104\097\115 \098\101\101\110 \114\101\103\105\115\116\101\114\101\100"),2)end local P=a(N("\099\109\100",z))local I={[0]=Y;appName=k,options=P;input=O;self=u,handler=u;uiType="\099\109\100";uiName=z}l(I,1,P,0)end function k.CreateChatCommand(Y,c,z)if not u then u=LibStub(N)end if u.RegisterChatCommand(Y,c,function(O)k.HandleCommand(Y,c,z,O)end,true)then O[c]=z end end function k.GetChatCommandOptions(z,c)return O[c]end
--- 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 1284 2022-09-25 09:15:30Z nevcairiel $

--[[
AceConfigCmd-3.0

Handles commandline optionstable access

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

]]

-- TODO: plugin args

local cfgreg = LibStub("AceConfigRegistry-3.0")

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

if not AceConfigCmd then return end

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

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

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 strInput = 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 type(tab.pattern)~="string" then err(info, inputpos, "'pattern' - expected a string") end
			if not strmatch(strInput, tab.pattern) then
				usererr(info, inputpos, "'"..strInput.."' - " .. L["invalid input"])
				return
			end
		end

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



	elseif tab.type=="toggle" then
		------------ toggle --------------------------------------------
		local b
		local str = strtrim(strlower(strInput))
		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(strInput)
		if not val then
			usererr(info, inputpos, "'"..strInput.."' - "..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(strInput))

		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(strInput))

		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 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(strInput))
		if str == "" then
			--TODO: Show current value
			return
		end

		local r, g, b, a

		local hasAlpha = tab.hasAlpha
		if type(hasAlpha) == "function" or type(hasAlpha) == "string" then
			info.hasAlpha = hasAlpha
			hasAlpha = callmethod(info, inputpos, tab, 'hasAlpha')
			info.hasAlpha = nil
		end

		if 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(strInput))
		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
