0721829 - Remove unused code
[wowui.git] / libs / AceConfig-3.0 / AceConfigCmd-3.0 / AceConfigCmd-3.0.lua
1 --- AceConfigCmd-3.0 handles access to an options table through the "command line" interface via the ChatFrames.
2 -- @class file
3 -- @name AceConfigCmd-3.0
4 -- @release $Id: AceConfigCmd-3.0.lua 1045 2011-12-09 17:58:40Z nevcairiel $
5
6 --[[
7 AceConfigCmd-3.0
8
9 Handles commandline optionstable access
10
11 REQUIRES: AceConsole-3.0 for command registration (loaded on demand)
12
13 ]]
14
15 -- TODO: plugin args
16
17
18 local MAJOR, MINOR = "AceConfigCmd-3.0", 13
19 local AceConfigCmd = LibStub:NewLibrary(MAJOR, MINOR)
20
21 if not AceConfigCmd then return end
22
23 AceConfigCmd.commands = AceConfigCmd.commands or {}
24 local commands = AceConfigCmd.commands
25
26 local cfgreg = LibStub("AceConfigRegistry-3.0")
27 local AceConsole -- LoD
28 local AceConsoleName = "AceConsole-3.0"
29
30 -- Lua APIs
31 local strsub, strsplit, strlower, strmatch, strtrim = string.sub, string.split, string.lower, string.match, string.trim
32 local format, tonumber, tostring = string.format, tonumber, tostring
33 local tsort, tinsert = table.sort, table.insert
34 local select, pairs, next, type = select, pairs, next, type
35 local error, assert = error, assert
36
37 -- WoW APIs
38 local _G = _G
39
40 -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
41 -- List them here for Mikk's FindGlobals script
42 -- GLOBALS: LibStub, SELECTED_CHAT_FRAME, DEFAULT_CHAT_FRAME
43
44
45 local L = setmetatable({}, {    -- TODO: replace with proper locale
46         __index = function(self,k) return k end
47 })
48
49
50
51 local function print(msg)
52         (SELECTED_CHAT_FRAME or DEFAULT_CHAT_FRAME):AddMessage(msg)
53 end
54
55 -- constants used by getparam() calls below
56
57 local handlertypes = {["table"]=true}
58 local handlermsg = "expected a table"
59
60 local functypes = {["function"]=true, ["string"]=true}
61 local funcmsg = "expected function or member name"
62
63
64 -- pickfirstset() - picks the first non-nil value and returns it
65
66 local function pickfirstset(...)        
67         for i=1,select("#",...) do
68                 if select(i,...)~=nil then
69                         return select(i,...)
70                 end
71         end
72 end
73
74
75 -- err() - produce real error() regarding malformed options tables etc
76
77 local function err(info,inputpos,msg )
78         local cmdstr=" "..strsub(info.input, 1, inputpos-1)
79         error(MAJOR..": /" ..info[0] ..cmdstr ..": "..(msg or "malformed options table"), 2)
80 end
81
82
83 -- usererr() - produce chatframe message regarding bad slash syntax etc
84
85 local function usererr(info,inputpos,msg )
86         local cmdstr=strsub(info.input, 1, inputpos-1);
87         print("/" ..info[0] .. " "..cmdstr ..": "..(msg or "malformed options table"))
88 end
89
90
91 -- callmethod() - call a given named method (e.g. "get", "set") with given arguments
92
93 local function callmethod(info, inputpos, tab, methodtype, ...)
94         local method = info[methodtype]
95         if not method then
96                 err(info, inputpos, "'"..methodtype.."': not set")
97         end
98
99         info.arg = tab.arg
100         info.option = tab
101         info.type = tab.type
102
103         if type(method)=="function" then
104                 return method(info, ...)
105         elseif type(method)=="string" then
106                 if type(info.handler[method])~="function" then
107                         err(info, inputpos, "'"..methodtype.."': '"..method.."' is not a member function of "..tostring(info.handler))
108                 end
109                 return info.handler[method](info.handler, info, ...)
110         else
111                 assert(false)   -- type should have already been checked on read
112         end
113 end
114
115 -- callfunction() - call a given named function (e.g. "name", "desc") with given arguments
116
117 local function callfunction(info, tab, methodtype, ...)
118         local method = tab[methodtype]
119
120         info.arg = tab.arg
121         info.option = tab
122         info.type = tab.type
123         
124         if type(method)=="function" then
125                 return method(info, ...)
126         else
127                 assert(false) -- type should have already been checked on read
128         end
129 end
130
131 -- do_final() - do the final step (set/execute) along with validation and confirmation
132
133 local function do_final(info, inputpos, tab, methodtype, ...)
134         if info.validate then 
135                 local res = callmethod(info,inputpos,tab,"validate",...)
136                 if type(res)=="string" then
137                         usererr(info, inputpos, "'"..strsub(info.input, inputpos).."' - "..res)
138                         return
139                 end
140         end
141         -- console ignores .confirm
142         
143         callmethod(info,inputpos,tab,methodtype, ...)
144 end
145
146
147 -- getparam() - used by handle() to retreive and store "handler", "get", "set", etc
148
149 local function getparam(info, inputpos, tab, depth, paramname, types, errormsg)
150         local old,oldat = info[paramname], info[paramname.."_at"]
151         local val=tab[paramname]
152         if val~=nil then
153                 if val==false then
154                         val=nil
155                 elseif not types[type(val)] then 
156                         err(info, inputpos, "'" .. paramname.. "' - "..errormsg) 
157                 end
158                 info[paramname] = val
159                 info[paramname.."_at"] = depth
160         end
161         return old,oldat
162 end
163
164
165 -- iterateargs(tab) - custom iterator that iterates both t.args and t.plugins.*
166 local dummytable={}
167
168 local function iterateargs(tab)
169         if not tab.plugins then 
170                 return pairs(tab.args) 
171         end
172         
173         local argtabkey,argtab=next(tab.plugins)
174         local v
175         
176         return function(_, k)
177                 while argtab do
178                         k,v = next(argtab, k)
179                         if k then return k,v end
180                         if argtab==tab.args then
181                                 argtab=nil
182                         else
183                                 argtabkey,argtab = next(tab.plugins, argtabkey)
184                                 if not argtabkey then
185                                         argtab=tab.args
186                                 end
187                         end
188                 end
189         end
190 end
191
192 local function checkhidden(info, inputpos, tab)
193         if tab.cmdHidden~=nil then
194                 return tab.cmdHidden
195         end
196         local hidden = tab.hidden
197         if type(hidden) == "function" or type(hidden) == "string" then
198                 info.hidden = hidden
199                 hidden = callmethod(info, inputpos, tab, 'hidden')
200                 info.hidden = nil
201         end
202         return hidden
203 end
204
205 local function showhelp(info, inputpos, tab, depth, noHead)
206         if not noHead then
207                 print("|cff33ff99"..info.appName.."|r: Arguments to |cffffff78/"..info[0].."|r "..strsub(info.input,1,inputpos-1)..":")
208         end
209         
210         local sortTbl = {}      -- [1..n]=name
211         local refTbl = {}   -- [name]=tableref
212         
213         for k,v in iterateargs(tab) do
214                 if not refTbl[k] then   -- a plugin overriding something in .args
215                         tinsert(sortTbl, k)
216                         refTbl[k] = v
217                 end
218         end
219         
220         tsort(sortTbl, function(one, two) 
221                 local o1 = refTbl[one].order or 100
222                 local o2 = refTbl[two].order or 100
223                 if type(o1) == "function" or type(o1) == "string" then
224                         info.order = o1
225                         info[#info+1] = one
226                         o1 = callmethod(info, inputpos, refTbl[one], "order")
227                         info[#info] = nil
228                         info.order = nil
229                 end
230                 if type(o2) == "function" or type(o1) == "string" then
231                         info.order = o2
232                         info[#info+1] = two
233                         o2 = callmethod(info, inputpos, refTbl[two], "order")
234                         info[#info] = nil
235                         info.order = nil
236                 end
237                 if o1<0 and o2<0 then return o1<o2 end
238                 if o2<0 then return true end
239                 if o1<0 then return false end
240                 if o1==o2 then return tostring(one)<tostring(two) end   -- compare names
241                 return o1<o2
242         end)
243         
244         for i = 1, #sortTbl do
245                 local k = sortTbl[i]
246                 local v = refTbl[k]
247                 if not checkhidden(info, inputpos, v) then
248                         if v.type ~= "description" and v.type ~= "header" then
249                                 -- recursively show all inline groups
250                                 local name, desc = v.name, v.desc
251                                 if type(name) == "function" then
252                                         name = callfunction(info, v, 'name')
253                                 end
254                                 if type(desc) == "function" then
255                                         desc = callfunction(info, v, 'desc')
256                                 end
257                                 if v.type == "group" and pickfirstset(v.cmdInline, v.inline, false) then
258                                         print("  "..(desc or name)..":")
259                                         local oldhandler,oldhandler_at = getparam(info, inputpos, v, depth, "handler", handlertypes, handlermsg)
260                                         showhelp(info, inputpos, v, depth, true)
261                                         info.handler,info.handler_at = oldhandler,oldhandler_at
262                                 else
263                                         local key = k:gsub(" ", "_")
264                                         print("  |cffffff78"..key.."|r - "..(desc or name or ""))
265                                 end
266                         end
267                 end
268         end
269 end
270
271
272 local function keybindingValidateFunc(text)
273         if text == nil or text == "NONE" then
274                 return nil
275         end
276         text = text:upper()
277         local shift, ctrl, alt
278         local modifier
279         while true do
280                 if text == "-" then
281                         break
282                 end
283                 modifier, text = strsplit('-', text, 2)
284                 if text then
285                         if modifier ~= "SHIFT" and modifier ~= "CTRL" and modifier ~= "ALT" then
286                                 return false
287                         end
288                         if modifier == "SHIFT" then
289                                 if shift then
290                                         return false
291                                 end
292                                 shift = true
293                         end
294                         if modifier == "CTRL" then
295                                 if ctrl then
296                                         return false
297                                 end
298                                 ctrl = true
299                         end
300                         if modifier == "ALT" then
301                                 if alt then
302                                         return false
303                                 end
304                                 alt = true
305                         end
306                 else
307                         text = modifier
308                         break
309                 end
310         end
311         if text == "" then
312                 return false
313         end
314         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
315                 return false
316         end
317         local s = text
318         if shift then
319                 s = "SHIFT-" .. s
320         end
321         if ctrl then
322                 s = "CTRL-" .. s
323         end
324         if alt then
325                 s = "ALT-" .. s
326         end
327         return s
328 end
329
330 -- handle() - selfrecursing function that processes input->optiontable 
331 -- - depth - starts at 0
332 -- - retfalse - return false rather than produce error if a match is not found (used by inlined groups)
333
334 local function handle(info, inputpos, tab, depth, retfalse)
335
336         if not(type(tab)=="table" and type(tab.type)=="string") then err(info,inputpos) end
337
338         -------------------------------------------------------------------
339         -- Grab hold of handler,set,get,func,etc if set (and remember old ones)
340         -- Note that we do NOT validate if method names are correct at this stage,
341         -- the handler may change before they're actually used!
342
343         local oldhandler,oldhandler_at = getparam(info,inputpos,tab,depth,"handler",handlertypes,handlermsg)
344         local oldset,oldset_at = getparam(info,inputpos,tab,depth,"set",functypes,funcmsg)
345         local oldget,oldget_at = getparam(info,inputpos,tab,depth,"get",functypes,funcmsg)
346         local oldfunc,oldfunc_at = getparam(info,inputpos,tab,depth,"func",functypes,funcmsg)
347         local oldvalidate,oldvalidate_at = getparam(info,inputpos,tab,depth,"validate",functypes,funcmsg)
348         --local oldconfirm,oldconfirm_at = getparam(info,inputpos,tab,depth,"confirm",functypes,funcmsg)
349         
350         -------------------------------------------------------------------
351         -- Act according to .type of this table
352                 
353         if tab.type=="group" then
354                 ------------ group --------------------------------------------
355                 
356                 if type(tab.args)~="table" then err(info, inputpos) end
357                 if tab.plugins and type(tab.plugins)~="table" then err(info,inputpos) end
358                 
359                 -- grab next arg from input
360                 local _,nextpos,arg = (info.input):find(" *([^ ]+) *", inputpos)
361                 if not arg then
362                         showhelp(info, inputpos, tab, depth)
363                         return
364                 end
365                 nextpos=nextpos+1
366                 
367                 -- loop .args and try to find a key with a matching name
368                 for k,v in iterateargs(tab) do
369                         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
370                         
371                         -- is this child an inline group? if so, traverse into it
372                         if v.type=="group" and pickfirstset(v.cmdInline, v.inline, false) then
373                                 info[depth+1] = k
374                                 if handle(info, inputpos, v, depth+1, true)==false then
375                                         info[depth+1] = nil
376                                         -- wasn't found in there, but that's ok, we just keep looking down here
377                                 else
378                                         return  -- done, name was found in inline group
379                                 end
380                         -- matching name and not a inline group
381                         elseif strlower(arg)==strlower(k:gsub(" ", "_")) then
382                                 info[depth+1] = k
383                                 return handle(info,nextpos,v,depth+1)
384                         end
385                 end
386                         
387                 -- no match 
388                 if retfalse then
389                         -- restore old infotable members and return false to indicate failure
390                         info.handler,info.handler_at = oldhandler,oldhandler_at
391                         info.set,info.set_at = oldset,oldset_at
392                         info.get,info.get_at = oldget,oldget_at
393                         info.func,info.func_at = oldfunc,oldfunc_at
394                         info.validate,info.validate_at = oldvalidate,oldvalidate_at
395                         --info.confirm,info.confirm_at = oldconfirm,oldconfirm_at
396                         return false
397                 end
398                 
399                 -- couldn't find the command, display error
400                 usererr(info, inputpos, "'"..arg.."' - " .. L["unknown argument"])
401                 return
402         end
403         
404         local str = strsub(info.input,inputpos);
405         
406         if tab.type=="execute" then
407                 ------------ execute --------------------------------------------
408                 do_final(info, inputpos, tab, "func")
409                 
410
411         
412         elseif tab.type=="input" then
413                 ------------ input --------------------------------------------
414                 
415                 local res = true
416                 if tab.pattern then
417                         if not(type(tab.pattern)=="string") then err(info, inputpos, "'pattern' - expected a string") end
418                         if not strmatch(str, tab.pattern) then
419                                 usererr(info, inputpos, "'"..str.."' - " .. L["invalid input"])
420                                 return
421                         end
422                 end
423                 
424                 do_final(info, inputpos, tab, "set", str)
425                 
426
427         
428         elseif tab.type=="toggle" then
429                 ------------ toggle --------------------------------------------
430                 local b
431                 local str = strtrim(strlower(str))
432                 if str=="" then
433                         b = callmethod(info, inputpos, tab, "get")
434
435                         if tab.tristate then
436                                 --cycle in true, nil, false order
437                                 if b then
438                                         b = nil
439                                 elseif b == nil then
440                                         b = false
441                                 else
442                                         b = true
443                                 end
444                         else
445                                 b = not b
446                         end
447                         
448                 elseif str==L["on"] then
449                         b = true
450                 elseif str==L["off"] then
451                         b = false
452                 elseif tab.tristate and str==L["default"] then
453                         b = nil
454                 else
455                         if tab.tristate then
456                                 usererr(info, inputpos, format(L["'%s' - expected 'on', 'off' or 'default', or no argument to toggle."], str))
457                         else
458                                 usererr(info, inputpos, format(L["'%s' - expected 'on' or 'off', or no argument to toggle."], str))
459                         end
460                         return
461                 end
462                 
463                 do_final(info, inputpos, tab, "set", b)
464                 
465
466         elseif tab.type=="range" then
467                 ------------ range --------------------------------------------
468                 local val = tonumber(str)
469                 if not val then
470                         usererr(info, inputpos, "'"..str.."' - "..L["expected number"])
471                         return
472                 end
473                 if type(info.step)=="number" then
474                         val = val- (val % info.step)
475                 end
476                 if type(info.min)=="number" and val<info.min then
477                         usererr(info, inputpos, val.." - "..format(L["must be equal to or higher than %s"], tostring(info.min)) )
478                         return
479                 end
480                 if type(info.max)=="number" and val>info.max then
481                         usererr(info, inputpos, val.." - "..format(L["must be equal to or lower than %s"], tostring(info.max)) )
482                         return
483                 end
484                 
485                 do_final(info, inputpos, tab, "set", val)
486
487         
488         elseif tab.type=="select" then
489                 ------------ select ------------------------------------
490                 local str = strtrim(strlower(str))
491                 
492                 local values = tab.values
493                 if type(values) == "function" or type(values) == "string" then
494                         info.values = values
495                         values = callmethod(info, inputpos, tab, "values")
496                         info.values = nil
497                 end
498                 
499                 if str == "" then
500                         local b = callmethod(info, inputpos, tab, "get")
501                         local fmt = "|cffffff78- [%s]|r %s"
502                         local fmt_sel = "|cffffff78- [%s]|r %s |cffff0000*|r"
503                         print(L["Options for |cffffff78"..info[#info].."|r:"])
504                         for k, v in pairs(values) do
505                                 if b == k then
506                                         print(fmt_sel:format(k, v))
507                                 else
508                                         print(fmt:format(k, v))
509                                 end
510                         end
511                         return
512                 end
513
514                 local ok
515                 for k,v in pairs(values) do 
516                         if strlower(k)==str then
517                                 str = k -- overwrite with key (in case of case mismatches)
518                                 ok = true
519                                 break
520                         end
521                 end
522                 if not ok then
523                         usererr(info, inputpos, "'"..str.."' - "..L["unknown selection"])
524                         return
525                 end
526                 
527                 do_final(info, inputpos, tab, "set", str)
528                 
529         elseif tab.type=="multiselect" then
530                 ------------ multiselect -------------------------------------------
531                 local str = strtrim(strlower(str))
532                 
533                 local values = tab.values
534                 if type(values) == "function" or type(values) == "string" then
535                         info.values = values
536                         values = callmethod(info, inputpos, tab, "values")
537                         info.values = nil
538                 end
539                 
540                 if str == "" then
541                         local fmt = "|cffffff78- [%s]|r %s"
542                         local fmt_sel = "|cffffff78- [%s]|r %s |cffff0000*|r"
543                         print(L["Options for |cffffff78"..info[#info].."|r (multiple possible):"])
544                         for k, v in pairs(values) do
545                                 if callmethod(info, inputpos, tab, "get", k) then
546                                         print(fmt_sel:format(k, v))
547                                 else
548                                         print(fmt:format(k, v))
549                                 end
550                         end
551                         return
552                 end
553                 
554                 --build a table of the selections, checking that they exist
555                 --parse for =on =off =default in the process
556                 --table will be key = true for options that should toggle, key = [on|off|default] for options to be set
557                 local sels = {}
558                 for v in str:gmatch("[^ ]+") do
559                         --parse option=on etc
560                         local opt, val = v:match('(.+)=(.+)')
561                         --get option if toggling
562                         if not opt then 
563                                 opt = v 
564                         end
565                         
566                         --check that the opt is valid
567                         local ok
568                         for k,v in pairs(values) do 
569                                 if strlower(k)==opt then
570                                         opt = k -- overwrite with key (in case of case mismatches)
571                                         ok = true
572                                         break
573                                 end
574                         end
575                         
576                         if not ok then
577                                 usererr(info, inputpos, "'"..opt.."' - "..L["unknown selection"])
578                                 return
579                         end
580                         
581                         --check that if val was supplied it is valid
582                         if val then
583                                 if val == L["on"] or val == L["off"] or (tab.tristate and val == L["default"]) then
584                                         --val is valid insert it
585                                         sels[opt] = val
586                                 else
587                                         if tab.tristate then
588                                                 usererr(info, inputpos, format(L["'%s' '%s' - expected 'on', 'off' or 'default', or no argument to toggle."], v, val))
589                                         else
590                                                 usererr(info, inputpos, format(L["'%s' '%s' - expected 'on' or 'off', or no argument to toggle."], v, val))
591                                         end
592                                         return
593                                 end
594                         else
595                                 -- no val supplied, toggle
596                                 sels[opt] = true
597                         end
598                 end
599                 
600                 for opt, val in pairs(sels) do
601                         local newval
602                         
603                         if (val == true) then
604                                 --toggle the option
605                                 local b = callmethod(info, inputpos, tab, "get", opt)
606                                 
607                                 if tab.tristate then
608                                         --cycle in true, nil, false order
609                                         if b then
610                                                 b = nil
611                                         elseif b == nil then
612                                                 b = false
613                                         else
614                                                 b = true
615                                         end
616                                 else
617                                         b = not b
618                                 end
619                                 newval = b
620                         else
621                                 --set the option as specified
622                                 if val==L["on"] then
623                                         newval = true
624                                 elseif val==L["off"] then
625                                         newval = false
626                                 elseif val==L["default"] then
627                                         newval = nil
628                                 end
629                         end
630                         
631                         do_final(info, inputpos, tab, "set", opt, newval)
632                 end
633                                         
634                 
635         elseif tab.type=="color" then
636                 ------------ color --------------------------------------------
637                 local str = strtrim(strlower(str))
638                 if str == "" then
639                         --TODO: Show current value
640                         return
641                 end
642                 
643                 local r, g, b, a
644                 
645                 local hasAlpha = tab.hasAlpha
646                 if type(hasAlpha) == "function" or type(hasAlpha) == "string" then
647                         info.hasAlpha = hasAlpha
648                         hasAlpha = callmethod(info, inputpos, tab, 'hasAlpha')
649                         info.hasAlpha = nil
650                 end
651                 
652                 if hasAlpha then
653                         if str:len() == 8 and str:find("^%x*$")  then
654                                 --parse a hex string
655                                 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
656                         else
657                                 --parse seperate values
658                                 r,g,b,a = str:match("^([%d%.]+) ([%d%.]+) ([%d%.]+) ([%d%.]+)$")
659                                 r,g,b,a = tonumber(r), tonumber(g), tonumber(b), tonumber(a)
660                         end
661                         if not (r and g and b and a) then
662                                 usererr(info, inputpos, format(L["'%s' - expected 'RRGGBBAA' or 'r g b a'."], str))
663                                 return
664                         end
665                         
666                         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
667                                 --values are valid
668                         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
669                                 --values are valid 0..255, convert to 0..1
670                                 r = r / 255
671                                 g = g / 255
672                                 b = b / 255
673                                 a = a / 255
674                         else
675                                 --values are invalid
676                                 usererr(info, inputpos, format(L["'%s' - values must all be either in the range 0..1 or 0..255."], str))
677                         end
678                 else
679                         a = 1.0
680                         if str:len() == 6 and str:find("^%x*$") then
681                                 --parse a hex string
682                                 r,g,b = tonumber(str:sub(1, 2), 16) / 255, tonumber(str:sub(3, 4), 16) / 255, tonumber(str:sub(5, 6), 16) / 255
683                         else
684                                 --parse seperate values
685                                 r,g,b = str:match("^([%d%.]+) ([%d%.]+) ([%d%.]+)$")
686                                 r,g,b = tonumber(r), tonumber(g), tonumber(b)
687                         end
688                         if not (r and g and b) then
689                                 usererr(info, inputpos, format(L["'%s' - expected 'RRGGBB' or 'r g b'."], str))
690                                 return
691                         end
692                         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
693                                 --values are valid
694                         elseif r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 then
695                                 --values are valid 0..255, convert to 0..1
696                                 r = r / 255
697                                 g = g / 255
698                                 b = b / 255
699                         else
700                                 --values are invalid
701                                 usererr(info, inputpos, format(L["'%s' - values must all be either in the range 0-1 or 0-255."], str))
702                         end
703                 end
704                 
705                 do_final(info, inputpos, tab, "set", r,g,b,a)
706
707         elseif tab.type=="keybinding" then
708                 ------------ keybinding --------------------------------------------
709                 local str = strtrim(strlower(str))
710                 if str == "" then
711                         --TODO: Show current value
712                         return
713                 end
714                 local value = keybindingValidateFunc(str:upper())
715                 if value == false then
716                         usererr(info, inputpos, format(L["'%s' - Invalid Keybinding."], str))
717                         return
718                 end
719
720                 do_final(info, inputpos, tab, "set", value)
721
722         elseif tab.type=="description" then
723                 ------------ description --------------------
724                 -- ignore description, GUI config only
725         else
726                 err(info, inputpos, "unknown options table item type '"..tostring(tab.type).."'")
727         end
728 end
729
730 --- Handle the chat command.
731 -- This is usually called from a chat command handler to parse the command input as operations on an aceoptions table.\\
732 -- AceConfigCmd uses this function internally when a slash command is registered with `:CreateChatCommand`
733 -- @param slashcmd The slash command WITHOUT leading slash (only used for error output)
734 -- @param appName The application name as given to `:RegisterOptionsTable()`
735 -- @param input The commandline input (as given by the WoW handler, i.e. without the command itself)
736 -- @usage
737 -- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceConsole-3.0")
738 -- -- Use AceConsole-3.0 to register a Chat Command
739 -- MyAddon:RegisterChatCommand("mychat", "ChatCommand")
740 -- 
741 -- -- Show the GUI if no input is supplied, otherwise handle the chat input.
742 -- function MyAddon:ChatCommand(input)
743 --   -- Assuming "MyOptions" is the appName of a valid options table
744 --   if not input or input:trim() == "" then
745 --     LibStub("AceConfigDialog-3.0"):Open("MyOptions")
746 --   else
747 --     LibStub("AceConfigCmd-3.0").HandleCommand(MyAddon, "mychat", "MyOptions", input)
748 --   end
749 -- end
750 function AceConfigCmd:HandleCommand(slashcmd, appName, input)
751
752         local optgetter = cfgreg:GetOptionsTable(appName)
753         if not optgetter then
754                 error([[Usage: HandleCommand("slashcmd", "appName", "input"): 'appName' - no options table "]]..tostring(appName)..[[" has been registered]], 2)
755         end
756         local options = assert( optgetter("cmd", MAJOR) )
757         
758         local info = {   -- Don't try to recycle this, it gets handed off to callbacks and whatnot
759                 [0] = slashcmd,
760                 appName = appName,
761                 options = options,
762                 input = input,
763                 self = self,
764                 handler = self,
765                 uiType = "cmd",
766                 uiName = MAJOR,
767         }
768         
769         handle(info, 1, options, 0)  -- (info, inputpos, table, depth)
770 end
771
772 --- Utility function to create a slash command handler.
773 -- Also registers tab completion with AceTab
774 -- @param slashcmd The slash command WITHOUT leading slash (only used for error output)
775 -- @param appName The application name as given to `:RegisterOptionsTable()`
776 function AceConfigCmd:CreateChatCommand(slashcmd, appName)
777         if not AceConsole then
778                 AceConsole = LibStub(AceConsoleName)
779         end
780         if AceConsole.RegisterChatCommand(self, slashcmd, function(input)
781                                 AceConfigCmd.HandleCommand(self, slashcmd, appName, input)      -- upgradable
782                 end,
783         true) then -- succesfully registered so lets get the command -> app table in
784                 commands[slashcmd] = appName
785         end
786 end
787
788 --- Utility function that returns the options table that belongs to a slashcommand.
789 -- Designed to be used for the AceTab interface.
790 -- @param slashcmd The slash command WITHOUT leading slash (only used for error output)
791 -- @return The options table associated with the slash command (or nil if the slash command was not registered)
792 function AceConfigCmd:GetChatCommandOptions(slashcmd)
793         return commands[slashcmd]
794 end