view libs/AceConsole-2.0/AceConsole-2.0.lua @ 1:c11ca1d8ed91

Version 0.1
author Flick <flickerstreak@gmail.com>
date Tue, 20 Mar 2007 21:03:57 +0000
parents
children
line wrap: on
line source
--[[
Name: AceConsole-2.0
Revision: $Rev: 19865 $
Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
Inspired By: Ace 1.x by Turan (turan@gryphon.com)
Website: http://www.wowace.com/
Documentation: http://www.wowace.com/index.php/AceConsole-2.0
SVN: http://svn.wowace.com/root/trunk/Ace2/AceConsole-2.0
Description: Mixin to allow for input/output capabilities. This uses the
             AceOptions data table format to determine input.
             http://wiki.wowace.com/index.php/AceOptions_data_table
Dependencies: AceLibrary, AceOO-2.0
]]

local MAJOR_VERSION = "AceConsole-2.0"
local MINOR_VERSION = "$Revision: 19865 $"

if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end

if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0.") end

local MAP_ONOFF, USAGE, IS_CURRENTLY_SET_TO, IS_NOW_SET_TO, IS_NOT_A_VALID_OPTION_FOR, IS_NOT_A_VALID_VALUE_FOR, NO_OPTIONS_AVAILABLE, OPTION_HANDLER_NOT_FOUND, OPTION_HANDLER_NOT_VALID, OPTION_IS_DISABLED, KEYBINDING_USAGE
if GetLocale() == "deDE" then
	MAP_ONOFF = { [false] = "|cffff0000Aus|r", [true] = "|cff00ff00An|r" }
	USAGE = "Benutzung"
	IS_CURRENTLY_SET_TO = "|cffffff7f%s|r steht momentan auf |cffffff7f[|r%s|cffffff7f]|r"
	IS_NOW_SET_TO = "|cffffff7f%s|r ist nun auf |cffffff7f[|r%s|cffffff7f]|r gesetzt"
	IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] ist keine g\195\188ltige Option f\195\188r |cffffff7f%s|r"
	IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] ist kein g\195\188ltiger Wert f\195\188r |cffffff7f%s|r"
	NO_OPTIONS_AVAILABLE = "Keine Optionen verfügbar"
	OPTION_HANDLER_NOT_FOUND = "Optionen handler |cffffff7f%q|r nicht gefunden."
	OPTION_HANDLER_NOT_VALID = "Optionen handler nicht g\195\188ltig."
	OPTION_IS_DISABLED = "Option |cffffff7f%s|r deaktiviert."
	KEYBINDING_USAGE = "<ALT-CTRL-SHIFT-KEY>" -- fix
elseif GetLocale() == "frFR" then
	MAP_ONOFF = { [false] = "|cffff0000Inactif|r", [true] = "|cff00ff00Actif|r" }
	USAGE = "Utilisation"
	IS_CURRENTLY_SET_TO = "|cffffff7f%s|r est actuellement positionn\195\169 sur |cffffff7f[|r%s|cffffff7f]|r"
	IS_NOW_SET_TO = "|cffffff7f%s|r est maintenant positionn\195\169 sur |cffffff7f[|r%s|cffffff7f]|r"
	IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] n'est pas une option valide pour |cffffff7f%s|r"
	IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] n'est pas une valeur valide pour |cffffff7f%s|r"
	NO_OPTIONS_AVAILABLE = "Pas d'options disponibles"
	OPTION_HANDLER_NOT_FOUND = "Le gestionnaire d'option |cffffff7f%q|r n'a pas \195\169t\195\169 trouv\195\169."
	OPTION_HANDLER_NOT_VALID = "Le gestionnaire d'option n'est pas valide."
	OPTION_IS_DISABLED = "L'option |cffffff7f%s|r est d\195\169sactiv\195\169e."
	KEYBINDING_USAGE = "<ALT-CTRL-SHIFT-KEY>" -- fix
elseif GetLocale() == "koKR" then
	MAP_ONOFF = { [false] = "|cffff0000끔|r", [true] = "|cff00ff00켬|r" }
	USAGE = "사용법"
	IS_CURRENTLY_SET_TO = "|cffffff7f%s|r|1은;는; 현재 상태는 |cffffff7f[|r%s|cffffff7f]|r|1으로;로; 설정되어 있습니다"
	IS_NOW_SET_TO = "|cffffff7f%s|r|1을;를; |cffffff7f[|r%s|cffffff7f]|r 상태로 변경합니다"
	IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r]|1은;는; |cffffff7f%s|r에서 사용불가능한 설정입니다"
	IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r]|1은;는; |cffffff7f%s|r에서 사용불가능한 설정값입니다"
	NO_OPTIONS_AVAILABLE = "가능한 설정이 없습니다"
	OPTION_HANDLER_NOT_FOUND = "설정 조정값인 |cffffff7f%q|r|1을;를; 찾지 못했습니다."
	OPTION_HANDLER_NOT_VALID = "설정 조정값이 올바르지 않습니다."
	OPTION_IS_DISABLED = "|cffffff7f%s|r 설정은 사용할 수 없습니다."
	KEYBINDING_USAGE = "<ALT-CTRL-SHIFT-KEY>" -- fix
elseif GetLocale() == "zhCN" then
	MAP_ONOFF = { [false] = "|cffff0000\229\133\179\233\151\173|r", [true] = "|cff00ff00\229\188\128\229\144\175|r" }
	USAGE = "\231\148\168\230\179\149"
	IS_CURRENTLY_SET_TO = "|cffffff7f%s|r \229\189\147\229\137\141\232\162\171\232\174\190\231\189\174 |cffffff7f[|r%s|cffffff7f]|r"
	IS_NOW_SET_TO = "|cffffff7f%s|r \231\142\176\229\156\168\232\162\171\232\174\190\231\189\174\228\184\186 |cffffff7f[|r%s|cffffff7f]|r"
	IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] \228\184\141\230\152\175\228\184\128\228\184\170\230\156\137\230\149\136\231\154\132\233\128\137\233\161\185 \228\184\186 |cffffff7f%s|r"
	IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] \228\184\141\230\152\175\228\184\128\228\184\170\230\156\137\230\149\136\229\128\188 \228\184\186 |cffffff7f%s|r"
	NO_OPTIONS_AVAILABLE = "\230\178\161\230\156\137\233\128\137\233\161\185\229\143\175\231\148\168"
	OPTION_HANDLER_NOT_FOUND = "\233\128\137\233\161\185\229\164\132\231\144\134\231\168\139\229\186\143 |cffffff7f%q|r \230\178\161\230\159\165\230\137\190."
	OPTION_HANDLER_NOT_VALID = "\233\128\137\233\161\185\229\164\132\231\144\134\231\168\139\229\186\143 \230\151\160\230\149\136."
	OPTION_IS_DISABLED = "\233\128\137\233\161\185 |cffffff7f%s|r \228\184\141\229\174\140\230\149\180."
	KEYBINDING_USAGE = "<ALT-CTRL-SHIFT-KEY>" -- fix
elseif GetLocale() == "zhTW" then
	MAP_ONOFF = { [false] = "|cffff0000關閉|r", [true] = "|cff00ff00開啟|r" }
	USAGE = "用法"
	IS_CURRENTLY_SET_TO = "|cffffff7f%s|r 目前的設定為 |cffffff7f[|r%s|cffffff7f]|r"
	IS_NOW_SET_TO = "|cffffff7f%s|r 現在被設定為 |cffffff7f[|r%s|cffffff7f]|r"
	IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] 是一個不符合規定的選項,對 |cffffff7f%s|r"
	IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] 是一個不符合規定的數值,對 |cffffff7f%s|r"
	NO_OPTIONS_AVAILABLE = "沒有可用的選項處理器。"
	OPTION_HANDLER_NOT_FOUND = "找不到 |cffffff7f%q|r 選項處理器。"
	OPTION_HANDLER_NOT_VALID = "選項處理器不符合規定。"
	OPTION_IS_DISABLED = "|cffffff7f%s|r 已被停用。"
	KEYBINDING_USAGE = "<ALT-CTRL-SHIFT-KEY>" -- fix
else -- enUS
	MAP_ONOFF = { [false] = "|cffff0000Off|r", [true] = "|cff00ff00On|r" }
	USAGE = "Usage"
	IS_CURRENTLY_SET_TO = "|cffffff7f%s|r is currently set to |cffffff7f[|r%s|cffffff7f]|r"
	IS_NOW_SET_TO = "|cffffff7f%s|r is now set to |cffffff7f[|r%s|cffffff7f]|r"
	IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] is not a valid option for |cffffff7f%s|r"
	IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] is not a valid value for |cffffff7f%s|r"
	NO_OPTIONS_AVAILABLE = "No options available"
	OPTION_HANDLER_NOT_FOUND = "Option handler |cffffff7f%q|r not found."
	OPTION_HANDLER_NOT_VALID = "Option handler not valid."
	OPTION_IS_DISABLED = "Option |cffffff7f%s|r is disabled."
	KEYBINDING_USAGE = "<ALT-CTRL-SHIFT-KEY>"
end

local NONE = NONE or "None"

local AceOO = AceLibrary("AceOO-2.0")
local AceEvent

local AceConsole = AceOO.Mixin { "Print", "PrintComma", "CustomPrint", "RegisterChatCommand" }
local Dewdrop

local _G = getfenv(0)

local function print(text, name, r, g, b, frame, delay)
	if not text or text:len() == 0 then
		text = " "
	end
	if not name or name == AceConsole then
		(frame or DEFAULT_CHAT_FRAME):AddMessage(text, r, g, b, nil, delay or 5)
	else
		(frame or DEFAULT_CHAT_FRAME):AddMessage("|cffffff78" .. tostring(name) .. ":|r " .. text, r, g, b, nil, delay or 5)
	end
end

local real_tostring = tostring

local function tostring(t)
	if type(t) == "table" then
		if type(rawget(t, 0)) == "userdata" and type(t.GetObjectType) == "function" then
			return string.format("<%s:%s>", t:GetObjectType(), t:GetName() or "(anon)")
		end
	end
	return real_tostring(t)
end

local function _tostring(...)
	if select('#', ...) < 1 then
		return
	end
	return tostring((...)), _tostring(select(2, ...))
end
function AceConsole:CustomPrint(r, g, b, frame, delay, connector, a1, ...)
	if tostring(a1):find("%%") and select('#', ...) >= 1 then
		local success, text = pcall(string.format, _tostring(a1, ...))
		if success then
			print(text, self, r, g, b, frame or self.printFrame, delay)
			return
		end
	end
	print((connector or " "):join(_tostring(a1, ...)), self, r, g, b, frame or self.printFrame, delay)
end

function AceConsole:Print(...)
	return AceConsole.CustomPrint(self, nil, nil, nil, nil, nil, " ", ...)
end

function AceConsole:PrintComma(...)
	return AceConsole.CustomPrint(self, nil, nil, nil, nil, nil, ", ", ...)
end

local work
local argwork

local function findTableLevel(self, options, chat, text, index, passTable)
	if not index then
		index = 1
		if work then
			for k,v in pairs(work) do
				work[k] = nil
			end
			for k,v in pairs(argwork) do
				argwork[k] = nil
			end
		else
			work = {}
			argwork = {}
		end
		local len = text:len()
		local count
		repeat
			text, count = text:gsub("(|cff%x%x%x%x%x%x|Hitem:%d-:%d-:%d-:%d-|h%[[^%]]-) (.-%]|h|r)", "%1\001%2")
		until count == 0
		text = text:gsub("(%]|h|r)(|cff%x%x%x%x%x%x|Hitem:%d-:%d-:%d-:%d-|h%[)", "%1 %2")
		for token in text:gmatch("([^%s]+)") do
			local token = token
			local num = tonumber(token)
			if num then
				token = num
			else
				token = token:gsub("\001", " ")
			end
			table.insert(work, token)
		end
	end
	
	local path = chat
	for i = 1, index - 1 do
		path = path .. " " .. tostring(work[i])
	end
	
	if type(options.args) == "table" then
		local disabled, hidden = options.disabled, options.cmdHidden or options.hidden
		if hidden then
			if type(hidden) == "function" then
				hidden = hidden()
			elseif type(hidden) == "string" then
				local handler = options.handler or self
				local f = hidden
				local neg = f:match("^~(.-)$")
				if neg then
					f = neg
				end
				if type(handler[f]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
				end
				hidden = handler[f](handler)
				if neg then
					hidden = not hidden
				end
			end
		end
		if hidden then
			disabled = true
		elseif disabled then
			if type(disabled) == "function" then
				disabled = disabled()
			elseif type(disabled) == "string" then
				local handler = options.handler or self
				local f = disabled
				local neg = f:match("^~(.-)$")
				if neg then
					f = neg
				end
				if type(handler[f]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
				end
				disabled = handler[f](handler)
				if neg then
					disabled = not disabled
				end
			end
		end
		if not disabled then
			local next = work[index] and work[index]:lower()
			if next then
				for k,v in pairs(options.args) do
					local good = false
					if k:lower() == next then
						good = true
					elseif type(v.aliases) == "table" then
						for _,alias in ipairs(v.aliases) do
							if alias:lower() == next then
								good = true
								break
							end
						end
					elseif type(v.aliases) == "string" and v.aliases:lower() == next then
						good = true
					end
					if good then
						return findTableLevel(options.handler or self, v, chat, text, index + 1, options.pass and options or nil)
					end
				end
			end
		end
	end
	for i = index, #work do
		table.insert(argwork, work[i])
	end
	return options, path, argwork, options.handler or self, passTable, passTable and work[index - 1]
end

local function validateOptionsMethods(self, options, position)
	if type(options) ~= "table" then
		return "Options must be a table.", position
	end
	self = options.handler or self
	if options.type == "execute" then
		if options.func and type(options.func) ~= "string" and type(options.func) ~= "function" then
			return "func must be a string or function", position
		end
		if options.func and type(options.func) == "string" and type(self[options.func]) ~= "function" then
			return string.format("%q is not a proper function", options.func), position
		end
	else
		if options.get then
			if type(options.get) ~= "string" and type(options.get) ~= "function" then
				return "get must be a string or function", position
			end
			if type(options.get) == "string" then
				local f = options.get
				if options.type == "toggle" then
					f = f:match("^~(.-)$") or f
				end
				if type(self[f]) ~= "function" then
					return string.format("%q is not a proper function", f), position
				end
			end
		end
		if options.set then
			if type(options.set) ~= "string" and type(options.set) ~= "function" then
				return "set must be a string or function", position
			end
			if type(options.set) == "string" and type(self[options.set]) ~= "function" then
				return string.format("%q is not a proper function", options.set), position
			end
		end
		if options.validate and type(options.validate) ~= "table" and options.validate ~= "keybinding" then
			if type(options.validate) ~= "string" and type(options.validate) ~= "function" then
				return "validate must be a string or function", position
			end
			if type(options.validate) == "string" and type(self[options.validate]) ~= "function" then
				return string.format("%q is not a proper function", options.validate), position
			end
		end
	end
	if options.disabled and type(options.disabled) == "string" then
		local f = options.disabled
		f = f:match("^~(.-)$") or f
		if type(self[f]) ~= "function" then
			return string.format("%q is not a proper function", f), position
		end
	end
	if options.cmdHidden and type(options.cmdHidden) == "string" then
		local f = options.cmdHidden
		f = f:match("^~(.-)$") or f
		if type(self[f]) ~= "function" then
			return string.format("%q is not a proper function", f), position
		end
	end
	if options.guiHidden and type(options.guiHidden) == "string" then
		local f = options.guiHidden
		f = f:match("^~(.-)$") or f
		if type(self[f]) ~= "function" then
			return string.format("%q is not a proper function", f), position
		end
	end
	if options.hidden and type(options.hidden) == "string" then
		local f = options.hidden
		f = f:match("^~(.-)$") or f
		if type(self[f]) ~= "function" then
			return string.format("%q is not a proper function", f), position
		end
	end
	if options.type == "group" and type(options.args) == "table" then
		for k,v in pairs(options.args) do
			if type(v) == "table" then
				local newposition
				if position then
					newposition = position .. ".args." .. k
				else
					newposition = "args." .. k
				end
				local err, pos = validateOptionsMethods(self, v, newposition)
				if err then
					return err, pos
				end
			end
		end
	end
end

local function validateOptions(options, position, baseOptions, fromPass)
	if not baseOptions then
		baseOptions = options
	end
	if type(options) ~= "table" then
		return "Options must be a table.", position
	end
	local kind = options.type
	if type(kind) ~= "string" then
		return '"type" must be a string.', position
	elseif kind ~= "group" and kind ~= "range" and kind ~= "text" and kind ~= "execute" and kind ~= "toggle" and kind ~= "color" and kind ~= "header" then
		return '"type" must either be "range", "text", "group", "toggle", "execute", "color", or "header".', position
	end
	if options.aliases then
		if type(options.aliases) ~= "table" and type(options.aliases) ~= "string" then
			return '"alias" must be a table or string', position
		end
	end
	if not fromPass then
		if kind == "execute" then
			if type(options.func) ~= "string" and type(options.func) ~= "function" then
				return '"func" must be a string or function', position
			end
		elseif kind == "range" or kind == "text" or kind == "toggle" then
			if type(options.set) ~= "string" and type(options.set) ~= "function" then
				return '"set" must be a string or function', position
			end
			if kind == "text" and options.get == false then
			elseif type(options.get) ~= "string" and type(options.get) ~= "function" then
				return '"get" must be a string or function', position
			end
		elseif kind == "group" and options.pass then
			if options.pass ~= true then
				return '"pass" must be either nil, true, or false', position
			end
			if not options.func then
				if type(options.set) ~= "string" and type(options.set) ~= "function" then
					return '"set" must be a string or function', position
				end
				if type(options.get) ~= "string" and type(options.get) ~= "function" then
					return '"get" must be a string or function', position
				end
			elseif type(options.func) ~= "string" and type(options.func) ~= "function" then
				return '"func" must be a string or function', position
			end
		end
	else
		if kind == "group" then
			return 'cannot have "type" = "group" as a subgroup of a passing group', position
		end
	end
	if options ~= baseOptions then
		if kind == "header" then
		elseif type(options.desc) ~= "string" then
			return '"desc" must be a string', position
		elseif options.desc:len() == 0 then
			return '"desc" cannot be a 0-length string', position
		end
	end

	if options ~= baseOptions or kind == "range" or kind == "text" or kind == "toggle" or kind == "color" then
		if options.type == "header" and not options.cmdName and not options.name then
		elseif options.cmdName then
			if type(options.cmdName) ~= "string" then
				return '"cmdName" must be a string or nil', position
			elseif options.cmdName:len() == 0 then
				return '"cmdName" cannot be a 0-length string', position
			end
			if type(options.guiName) ~= "string" then
				if not options.guiNameIsMap then
					return '"guiName" must be a string or nil', position
				end
			elseif options.guiName:len() == 0 then
				return '"guiName" cannot be a 0-length string', position
			end
		else
			if type(options.name) ~= "string" then
				return '"name" must be a string', position
			elseif options.name:len() == 0 then
				return '"name" cannot be a 0-length string', position
			end
		end
	end
	if options.guiNameIsMap then
		if type(options.guiNameIsMap) ~= "boolean" then
			return '"guiNameIsMap" must be a boolean or nil', position
		elseif options.type ~= "toggle" then
			return 'if "guiNameIsMap" is true, then "type" must be set to \'toggle\'', position
		elseif type(options.map) ~= "table" then
			return '"map" must be a table', position
		end
	end
	if options.message and type(options.message) ~= "string" then
		return '"message" must be a string or nil', position
	end
	if options.error and type(options.error) ~= "string" then
		return '"error" must be a string or nil', position
	end
	if options.current and type(options.current) ~= "string" then
		return '"current" must be a string or nil', position
	end
	if options.order then
		if type(options.order) ~= "number" or (-1 < options.order and options.order < 0.999) then
			return '"order" must be a non-zero number or nil', position
		end
	end
	if options.disabled then
		if type(options.disabled) ~= "function" and type(options.disabled) ~= "string" and options.disabled ~= true then
			return '"disabled" must be a function, string, or boolean', position
		end
	end
	if options.cmdHidden then
		if type(options.cmdHidden) ~= "function" and type(options.cmdHidden) ~= "string" and options.cmdHidden ~= true then
			return '"cmdHidden" must be a function, string, or boolean', position
		end
	end
	if options.guiHidden then
		if type(options.guiHidden) ~= "function" and type(options.guiHidden) ~= "string" and options.guiHidden ~= true then
			return '"guiHidden" must be a function, string, or boolean', position
		end
	end
	if options.hidden then
		if type(options.hidden) ~= "function" and type(options.hidden) ~= "string" and options.hidden ~= true then
			return '"hidden" must be a function, string, or boolean', position
		end
	end
	if kind == "text" then
		if type(options.validate) == "table" then
			local t = options.validate
			local iTable = nil
			for k,v in pairs(t) do
				if type(k) == "number" then
					if iTable == nil then
						iTable = true
					elseif not iTable then
						return '"validate" must either have all keys be indexed numbers or strings', position
					elseif k < 1 or k > #t then
						return '"validate" numeric keys must be indexed properly. >= 1 and <= #validate', position
					end
				else
					if iTable == nil then
						iTable = false
					elseif iTable then
						return '"validate" must either have all keys be indexed numbers or strings', position
					end
				end
				if type(v) ~= "string" then
					return '"validate" values must all be strings', position
				end
			end
		elseif options.validate == "keybinding" then
			
		else
			if type(options.usage) ~= "string" then
				return '"usage" must be a string', position
			elseif options.validate and type(options.validate) ~= "string" and type(options.validate) ~= "function" then
				return '"validate" must be a string, function, or table', position
			end
		end
	elseif kind == "range" then
		if options.min or options.max then
			if type(options.min) ~= "number" then
				return '"min" must be a number', position
			elseif type(options.max) ~= "number" then
				return '"max" must be a number', position
			elseif options.min >= options.max then
				return '"min" must be less than "max"', position
			end
		end
		if options.step then
			if type(options.step) ~= "number" then
				return '"step" must be a number', position
			elseif options.step < 0 then
				return '"step" must be nonnegative', position
			end
		end
		if options.isPercent and options.isPercent ~= true then
			return '"isPercent" must either be nil, true, or false', position
		end
	elseif kind == "toggle" then
		if options.map then
			if type(options.map) ~= "table" then
				return '"map" must be a table', position
			elseif type(options.map[true]) ~= "string" then
				return '"map[true]" must be a string', position
			elseif type(options.map[false]) ~= "string" then
				return '"map[false]" must be a string', position
			end
		end
	elseif kind == "color" then
		if options.hasAlpha and options.hasAlpha ~= true then
			return '"hasAlpha" must be nil, true, or false', position
		end
	elseif kind == "group" then
		if options.pass and options.pass ~= true then
			return '"pass" must be nil, true, or false', position
		end
		if type(options.args) ~= "table" then
			return '"args" must be a table', position
		end
		for k,v in pairs(options.args) do
			if type(k) ~= "string" then
				return '"args" keys must be strings', position
			elseif k:find("%s") then
				return string.format('"args" keys must not include spaces. %q is not appropriate.', k), position
			elseif k:len() == 0 then
				return '"args" keys must not be 0-length strings.', position
			end
			if type(v) ~= "table" then
				return '"args" values must be tables', position and position .. "." .. k or k
			end
			local newposition
			if position then
				newposition = position .. ".args." .. k
			else
				newposition = "args." .. k
			end
			local err, pos = validateOptions(v, newposition, baseOptions, options.pass)
			if err then
				return err, pos
			end
		end
	end
end

local colorTable
local colorFunc
local colorCancelFunc

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 not text:find("^F%d+$") 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
AceConsole.keybindingValidateFunc = keybindingValidateFunc

local order

local mysort_args
local mysort

local function printUsage(self, handler, realOptions, options, path, args, quiet, filter)
	if filter then
		filter = "^" .. filter:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
	end
	local hidden, disabled = options.cmdHidden or options.hidden, options.disabled
	if hidden then
		if type(hidden) == "function" then
			hidden = hidden()
		elseif type(hidden) == "string" then
			local f = hidden
			local neg = f:match("^~(.-)$")
			if neg then
				f = neg
			end
			if type(handler[f]) ~= "function" then
				AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
			end
			hidden = handler[f](handler)
			if neg then
				hidden = not hidden
			end
		end
	end
	if hidden then
		disabled = true
	elseif disabled then
		if type(disabled) == "function" then
			disabled = disabled()
		elseif type(disabled) == "string" then
			local f = disabled
			local neg = f:match("^~(.-)$")
			if neg then
				f = neg
			end
			if type(handler[f]) ~= "function" then
				AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
			end
			disabled = handler[f](handler)
			if neg then
				disabled = not disabled
			end
		end
	end
	local kind = (options.type or "group"):lower()
	if disabled then
		print(string.format(OPTION_IS_DISABLED, path), realOptions.cmdName or realOptions.name or self)
	elseif kind == "text" then
		local var
		if passTable then
			if not passTable.get then
			elseif type(passTable.get) == "function" then
				var = passTable.get(passValue)
			else
				local handler = passTable.handler or handler
				if type(handler[passTable.get]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
				end
				var = handler[passTable.get](handler, passValue)
			end
		else
			if not options.get then
			elseif type(options.get) == "function" then
				var = options.get()
			else
				local handler = options.handler or handler
				if type(handler[options.get]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.get))
				end
				var = handler[options.get](handler)
			end
		end
		
		local usage
		if type(options.validate) == "table" then
			if filter then
				if not order then
					order = {}
				end
				for k,v in pairs(options.validate) do
					if v:find(filter) then
						table.insert(order, v)
					end
				end
				usage = "{" .. table.concat(order, " || ") .. "}"
				for k in pairs(order) do
					order[k] = nil
				end
			else
				if not order then
					order = {}
				end
				for k,v in pairs(options.validate) do
					table.insert(order, v)
				end
				usage = "{" .. table.concat(order, " || ") .. "}"
				for k in pairs(order) do
					order[k] = nil
				end
			end
			var = options.validate[var] or var
		elseif options.validate == "keybinding" then
			usage = KEYBINDING_USAGE
		else
			usage = options.usage or "<value>"
		end
		if not quiet then
			print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, usage), realOptions.cmdName or realOptions.name or self)
		end
		if (passTable and passTable.get) or options.get then
			print(string.format(options.current or IS_CURRENTLY_SET_TO, tostring(options.cmdName or options.name), tostring(var or NONE)))
		end
	elseif kind == "range" then
		local var
		if passTable then
			if type(passTable.get) == "function" then
				var = passTable.get(passValue)
			else
				local handler = passTable.handler or handler
				if type(handler[passTable.get]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
				end
				var = handler[passTable.get](handler, passValue)
			end
		else
			if type(options.get) == "function" then
				var = options.get()
			else
				local handler = options.handler or handler
				if type(handler[options.get]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.get))
				end
				var = handler[options.get](handler)
			end
		end
		
		local usage
		local min = options.min or 0
		local max = options.max or 1
		if options.isPercent then
			min, max = min * 100, max * 100
			var = tostring(var * 100) .. "%"
		end
		local bit = "-"
		if min < 0 or max < 0 then
			bit = " - "
		end
		usage = string.format("(%s%s%s)", min, bit, max)
		if not quiet then
			print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, usage), realOptions.cmdName or realOptions.name or self)
		end
		print(string.format(options.current or IS_CURRENTLY_SET_TO, tostring(options.cmdName or options.name), tostring(var or NONE)))
	elseif kind == "group" then
		local usage
		if next(options.args) then
			if not order then
				order = {}
			end
			for k,v in pairs(options.args) do
				if v.type ~= "header" then
					local hidden = v.cmdHidden or v.hidden
					if hidden then
						if type(hidden) == "function" then
							hidden = hidden()
						elseif type(hidden) == "string" then
							local f = hidden
							local neg = f:match("^~(.-)$")
							if neg then
								f = neg
							end
							if type(handler[f]) ~= "function" then
								AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
							end
							hidden = handler[f](handler)
							if neg then
								hidden = not hidden
							end
						end
					end
					if not hidden then
						if filter then
							if k:find(filter) then
								table.insert(order, k)
							elseif type(v.aliases) == "table" then
								for _,bit in ipairs(v.aliases) do
									if bit:find(filter) then
										table.insert(order, k)
										break
									end
								end
							elseif type(v.aliases) == "string" then
								if v.aliases:find(filter) then
									table.insert(order, k)
								end
							end
						else
							table.insert(order, k)
						end
					end
				end
			end
			if not mysort then
				mysort = function(a, b)
					local alpha, bravo = mysort_args[a], mysort_args[b]
					local alpha_order = alpha and alpha.order or 100
					local bravo_order = bravo and bravo.order or 100
					if alpha_order == bravo_order then
						return tostring(a) < tostring(b)
					else
						if alpha_order < 0 then
							if bravo_order > 0 then
								return false
							end
						else
							if bravo_order < 0 then
								return true
							end
						end
						if alpha_order > 0 and bravo_order > 0 then
							return tostring(a) < tostring(b)
						end
						return alpha_order < bravo_order
					end
				end
			end
			mysort_args = options.args
			table.sort(order, mysort)
			mysort_args = nil
			if not quiet then
				if options == realOptions then
					if options.desc then
						print(tostring(options.desc), realOptions.cmdName or realOptions.name or self)
						print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, "{" .. table.concat(order, " || ") .. "}"))
					elseif self.description or self.notes then
						print(tostring(self.description or self.notes), realOptions.cmdName or realOptions.name or self)
						print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, "{" .. table.concat(order, " || ") .. "}"))
					else
						print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, "{" .. table.concat(order, " || ") .. "}"), realOptions.cmdName or realOptions.name or self)
					end
				else
					if options.desc then
						print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, "{" .. table.concat(order, " || ") .. "}"), realOptions.cmdName or realOptions.name or self)
						print(tostring(options.desc))
					else
						print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, "{" .. table.concat(order, " || ") .. "}"), realOptions.cmdName or realOptions.name or self)
					end
				end
			end
			for _,k in ipairs(order) do
				local v = options.args[k]
				if v then
					local disabled = v.disabled
					if disabled then
						if type(disabled) == "function" then
							disabled = disabled()
						elseif type(disabled) == "string" then
							local f = disabled
							local neg = f:match("^~(.-)$")
							if neg then
								f = neg
							end
							if type(handler[f]) ~= "function" then
								AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
							end
							disabled = handler[f](handler)
							if neg then
								disabled = not disabled
							end
						end
					end
					if type(v.aliases) == "table" then
						k = k .. " || " .. table.concat(v.aliases, " || ")
					elseif type(v.aliases) == "string" then
						k = k .. " || " .. v.aliases
					end
					if v.get then
						local a1,a2,a3,a4
						if type(v.get) == "function" then
							if options.pass then
								a1,a2,a3,a4 = v.get(k)
							else
								a1,a2,a3,a4 = v.get()
							end
						else
							local handler = v.handler or handler
							local f = v.get
							local neg
							if v.type == "toggle" then
								neg = f:match("^~(.-)$")
								if neg then
									f = neg
								end
							end
							if type(handler[f]) ~= "function" then
								AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
							end
							if options.pass then
								a1,a2,a3,a4 = handler[f](handler, k)
							else
								a1,a2,a3,a4 = handler[f](handler)
							end
							if neg then
								a1 = not a1
							end
						end
						if v.type == "color" then
							if v.hasAlpha then
								if not a1 or not a2 or not a3 or not a4 then
									s = NONE
								else
									s = string.format("|c%02x%02x%02x%02x%02x%02x%02x%02x|r", a4*255, a1*255, a2*255, a3*255, a4*255, a1*255, a2*255, a3*255)
								end
							else
								if not a1 or not a2 or not a3 then
									s = NONE
								else
									s = string.format("|cff%02x%02x%02x%02x%02x%02x|r", a1*255, a2*255, a3*255, a1*255, a2*255, a3*255)
								end
							end
						elseif v.type == "toggle" then
							if v.map then
								s = tostring(v.map[a1 and true or false] or NONE)
							else
								s = tostring(MAP_ONOFF[a1 and true or false] or NONE)
							end
						elseif v.type == "range" then
							if v.isPercent then
								s = tostring(a1 * 100) .. "%"
							else
								s = tostring(a1)
							end
						elseif v.type == "text" and type(v.validate) == "table" then
							s = tostring(v.validate[a1] or a1 or NONE)
						else
							s = tostring(a1 or NONE)
						end
						if disabled then
							local s = s:gsub("|cff%x%x%x%x%x%x(.-)|r", "%1")
							local desc = (v.desc or NONE):gsub("|cff%x%x%x%x%x%x(.-)|r", "%1")
							print(string.format("|cffcfcfcf - %s: [%s] %s|r", k, s, desc))
						else
							print(string.format(" - |cffffff7f%s: [|r%s|cffffff7f]|r %s", k, s, v.desc or NONE))
						end
					else
						if disabled then
							local desc = (v.desc or NONE):gsub("|cff%x%x%x%x%x%x(.-)|r", "%1")
							print(string.format("|cffcfcfcf - %s: %s", k, desc))
						else
							print(string.format(" - |cffffff7f%s:|r %s", k, v.desc or NONE))
						end
					end
				end
			end
			for k in pairs(order) do
				order[k] = nil
			end
		else
			if options.desc then
				desc = options.desc
				print(string.format("|cffffff7f%s:|r %s", USAGE, path), realOptions.cmdName or realOptions.name or self)
				print(tostring(options.desc))
			elseif options == realOptions and (self.description or self.notes) then
				print(tostring(self.description or self.notes), realOptions.cmdName or realOptions.name or self)
				print(string.format("|cffffff7f%s:|r %s", USAGE, path))
			else
				print(string.format("|cffffff7f%s:|r %s", USAGE, path), realOptions.cmdName or realOptions.name or self)
			end
			print(NO_OPTIONS_AVAILABLE)
		end
	end
end

local function handlerFunc(self, chat, msg, options)
	if not msg then
		msg = ""
	else
		msg = msg:gsub("^%s*(.-)%s*$", "%1")
		msg = msg:gsub("%s+", " ")
	end
	
	local realOptions = options
	local options, path, args, handler, passTable, passValue = findTableLevel(self, options, chat, msg)
	
	local hidden, disabled = options.cmdHidden or options.hidden, options.disabled
	if hidden then
		if type(hidden) == "function" then
			hidden = hidden()
		elseif type(hidden) == "string" then
			local f = hidden
			local neg = f:match("^~(.-)$")
			if neg then
				f = neg
			end
			if type(handler[f]) ~= "function" then
				AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
			end
			hidden = handler[f](handler)
			if neg then
				hidden = not hidden
			end
		end
	end
	if hidden then
		disabled = true
	elseif disabled then
		if type(disabled) == "function" then
			disabled = disabled()
		elseif type(disabled) == "string" then
			local f = disabled
			local neg = f:match("^~(.-)$")
			if neg then
				f = neg
			end
			if type(handler[f]) ~= "function" then
				AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
			end
			disabled = handler[f](handler)
			if neg then
				disabled = not disabled
			end
		end
	end
	local _G_this = this
	local kind = (options.type or "group"):lower()
	if disabled then
		print(string.format(OPTION_IS_DISABLED, path), realOptions.cmdName or realOptions.name or self)
	elseif kind == "text" then
		if #args > 0 then
			if (type(options.validate) == "table" and #args > 1) or (type(options.validate) ~= "table" and not options.input) then
				local arg = table.concat(args, " ")
				for k,v in pairs(args) do
					args[k] = nil
				end
				args[1] = arg
			end
			if options.validate then
				local good
				if type(options.validate) == "function" then
					good = options.validate(unpack(args))
				elseif type(options.validate) == "table" then
					local arg = args[1]
					arg = tostring(arg):lower()
					for k,v in pairs(options.validate) do
						if v:lower() == arg then
							args[1] = type(k) == "string" and k or v
							good = true
							break
						end
					end
					if not good and type((next(options.validate))) == "string" then
						for k,v in pairs(options.validate) do
							if type(k) == "string" and k:lower() == arg then
								args[1] = k
								good = true
								break
							end
						end
					end
				elseif options.validate == "keybinding" then
					good = keybindingValidateFunc(unpack(args))
					if good ~= false then
						args[1] = good
					end
				else
					if type(handler[options.validate]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.validate))
					end
					good = handler[options.validate](handler, unpack(args))
				end
				if not good then
					local usage
					if type(options.validate) == "table" then
						if not order then
							order = {}
						end
						for k,v in pairs(options.validate) do
							table.insert(order, v)
						end
						usage = "{" .. table.concat(order, " || ") .. "}"
						for k in pairs(order) do
							order[k] = nil
						end
					elseif options.validate == "keybinding" then
						usage = KEYBINDING_USAGE
					else
						usage = options.usage or "<value>"
					end
					print(string.format(options.error or IS_NOT_A_VALID_OPTION_FOR, tostring(table.concat(args, " ")), path), realOptions.cmdName or realOptions.name or self)
					print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, usage))
					return
				end
			end
			
			local var
			if passTable then
				if not passTable.get then
				elseif type(passTable.get) == "function" then
					var = passTable.get(passValue)
				else
					if type(handler[passTable.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
					end
					var = handler[passTable.get](handler, passValue)
				end
			else
				if not options.get then
				elseif type(options.get) == "function" then
					var = options.get()
				else
					if type(handler[options.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.get))
					end
					var = handler[options.get](handler)
				end
			end
			
			if var ~= args[1] then
				if passTable then
					if type(passTable.set) == "function" then
						passTable.set(passValue, unpack(args))
					else
						if type(handler[passTable.set]) ~= "function" then
							AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.set))
						end
						handler[passTable.set](handler, passTable.set, unpack(args))
					end
				else
					if type(options.set) == "function" then
						options.set(unpack(args))
					else
						if type(handler[options.set]) ~= "function" then
							AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.set))
						end
						handler[options.set](handler, unpack(args))
					end
				end
			end
		end
		
		if #args > 0 then
			local var
			if passTable then
				if not passTable.get then
				elseif type(passTable.get) == "function" then
					var = passTable.get(passValue)
				else
					if type(handler[passTable.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
					end
					var = handler[passTable.get](handler, passValue)
				end
			else
				if not options.get then
				elseif type(options.get) == "function" then
					var = options.get()
				else
					if type(handler[options.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.get))
					end
					var = handler[options.get](handler)
				end
			end
			if type(options.validate) == "table" then
				var = options.validate[var] or var
			end
			if (passTable and passTable.get) or options.get then
				print(string.format(options.message or IS_NOW_SET_TO, tostring(options.cmdName or options.name), tostring(var or NONE)), realOptions.cmdName or realOptions.name or self)
			end
			if var == args[1] then
				return
			end
		else
			printUsage(self, handler, realOptions, options, path, args)
			return
		end
	elseif kind == "execute" then
		if passTable then
			if type(passFunc) == "function" then
				set(passValue)
			else
				if type(handler[passFunc]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passFunc))
				end
				handler[passFunc](handler, passValue)
			end
		else
			local ret, msg
			if type(options.func) == "function" then
				options.func()
			else
				local handler = options.handler or self
				if type(handler[options.func]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.func))
				end
				handler[options.func](handler)
			end
		end
	elseif kind == "toggle" then
		local var
		if passTable then
			if type(passTable.get) == "function" then
				var = passTable.get(passValue)
			else
				local f = passTable.get
				local neg = f:match("^~(.-)$")
				if neg then
					f = neg
				end
				if type(handler[f]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
				end
				var = handler[f](handler, passValue)
				if neg then
					var = not var
				end
			end
			if type(passTable.set) == "function" then
				passTable.set(passValue, not var)
			else
				if type(handler[passTable.set]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.set))
				end
				handler[passTable.set](handler, passValue, not var)
			end
			if type(passTable.get) == "function" then
				var = passTable.get(passValue)
			else
				local f = passTable.get
				local neg = f:match("^~(.-)$")
				if neg then
					f = neg
				end
				var = handler[f](handler, passValue)
				if neg then
					var = not var
				end
			end
		else
			local handler = options.handler or self
			if type(options.get) == "function" then
				var = options.get()
			else
				local f = options.get
				local neg = f:match("^~(.-)$")
				if neg then
					f = neg
				end
				if type(handler[f]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, f))
				end
				var = handler[f](handler)
				if neg then
					var = not var
				end
			end
			if type(options.set) == "function" then
				options.set(not var)
			else
				if type(handler[options.set]) ~= "function" then
					AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.set))
				end
				handler[options.set](handler, not var)
			end
			if type(options.get) == "function" then
				var = options.get()
			else
				local f = options.get
				local neg = f:match("^~(.-)$")
				if neg then
					f = neg
				end
				var = handler[f](handler)
				if neg then
					var = not var
				end
			end
		end
		
		print(string.format(options.message or IS_NOW_SET_TO, tostring(options.cmdName or options.name), (options.map or MAP_ONOFF)[var and true or false] or NONE), realOptions.cmdName or realOptions.name or self)
	elseif kind == "range" then
		local arg
		if #args <= 1 then
			arg = args[1]
		else
			arg = table.concat(args, " ")
		end

		if arg then
			local min = options.min or 0
			local max = options.max or 1
			local good = false
			if type(arg) == "number" then
				if options.isPercent then
					arg = arg / 100
				end

				if arg >= min and arg <= max then
					good = true
				end
				
				if good and type(options.step) == "number" and options.step > 0 then
					local step = options.step
					arg = math.floor((arg - min) / step + 0.5) * step + min
					if arg > max then
						arg = max
					elseif arg < min then
						arg = min
					end
				end
			end
			if not good then
				local usage
				local min = options.min or 0
				local max = options.max or 1
				if options.isPercent then
					min, max = min * 100, max * 100
				end
				local bit = "-"
				if min < 0 or max < 0 then
					bit = " - "
				end
				usage = string.format("(%s%s%s)", min, bit, max)
				print(string.format(options.error or IS_NOT_A_VALID_VALUE_FOR, tostring(arg), path), realOptions.cmdName or realOptions.name or self)
				print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, usage))
				return
			end
			
			local var
			if passTable then
				if type(passTable.get) == "function" then
					var = passTable.get(passValue)
				else
					if type(handler[passTable.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
					end
					var = handler[passTable.get](handler, passValue)
				end
			else
				if type(options.get) == "function" then
					var = options.get()
				else
					local handler = options.handler or self
					if type(handler[options.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.get))
					end
					var = handler[options.get](handler)
				end
			end
			
			if var ~= arg then
				if passTable then
					if type(passTable.set) == "function" then
						passTable.set(passValue, arg)
					else
						if type(handler[passTable.set]) ~= "function" then
							AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.set))
						end
						handler[passTable.set](handler, passValue, arg)
					end
				else
					if type(options.set) == "function" then
						options.set(arg)
					else
						local handler = options.handler or self
						if type(handler[options.set]) ~= "function" then
							AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.set))
						end
						handler[options.set](handler, arg)
					end
				end
			end
		end
		
		if arg then
			local var
			if passTable then
				if type(passTable.get) == "function" then
					var = passTable.get(passValue)
				else
					if type(handler[passTable.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
					end
					var = handler[passTable.get](handler, passValue)
				end
			else
				if type(options.get) == "function" then
					var = options.get()
				else
					local handler = options.handler or self
					if type(handler[options.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.get))
					end
					var = handler[options.get](handler)
				end
			end
			
			if var and options.isPercent then
				var = tostring(var * 100) .. "%"
			end
			print(string.format(options.message or IS_NOW_SET_TO, tostring(options.cmdName or options.name), tostring(var or NONE)), realOptions.cmdName or realOptions.name or self)
			if var == arg then
				return
			end
		else
			printUsage(self, handler, realOptions, options, path, args)
			return
		end
	elseif kind == "color" then
		if #args > 0 then
			local r,g,b,a
			if #args == 1 then
				local arg = tostring(args[1])
				if options.hasAlpha then
					if arg:len() == 8 and arg:find("^%x*$")  then
						r,g,b,a = tonumber(arg:sub(1, 2), 16) / 255, tonumber(arg:sub(3, 4), 16) / 255, tonumber(arg:sub(5, 6), 16) / 255, tonumber(arg:sub(7, 8), 16) / 255
					end
				else
					if arg:len() == 6 and arg:find("^%x*$") then
						r,g,b = tonumber(arg:sub(1, 2), 16) / 255, tonumber(arg:sub(3, 4), 16) / 255, tonumber(arg:sub(5, 6), 16) / 255
					end
				end
			elseif #args == 4 and options.hasAlpha then
				local a1,a2,a3,a4 = args[1], args[2], args[3], args[4]
				if type(a1) == "number" and type(a2) == "number" and type(a3) == "number" and type(a4) == "number" and a1 <= 1 and a2 <= 1 and a3 <= 1 and a4 <= 1 then
					r,g,b,a = a1,a2,a3,a4
				elseif (type(a1) == "number" or a1:len() == 2) and a1:find("^%x*$") and (type(a2) == "number" or a2:len() == 2) and a2:find("^%x*$") and (type(a3) == "number" or a3:len() == 2) and a3:find("^%x*$") and (type(a4) == "number" or a4:len() == 2) and a4:find("^%x*$") then
					r,g,b,a = tonumber(a1, 16) / 255, tonumber(a2, 16) / 255, tonumber(a3, 16) / 255, tonumber(a4, 16) / 255
				end
			elseif #args == 3 and not options.hasAlpha then
				local a1,a2,a3 = args[1], args[2], args[3]
				if type(a1) == "number" and type(a2) == "number" and type(a3) == "number" and a1 <= 1 and a2 <= 1 and a3 <= 1 then
					r,g,b = a1,a2,a3
				elseif (type(a1) == "number" or a1:len() == 2) and a1:find("^%x*$") and (type(a2) == "number" or a2:len() == 2) and a2:find("^%x*$") and (type(a3) == "number" or a3:len() == 2) and a3:find("^%x*$") then
					r,g,b = tonumber(a1, 16) / 255, tonumber(a2, 16) / 255, tonumber(a3, 16) / 255
				end
			end
			if not r then
				print(string.format(options.error or IS_NOT_A_VALID_OPTION_FOR, table.concat(args, ' '), path), realOptions.cmdName or realOptions.name or self)
				print(string.format("|cffffff7f%s:|r %s {0-1} {0-1} {0-1}%s", USAGE, path, options.hasAlpha and " {0-1}" or ""))
				return
			end
			if passTable then
				if type(passTable.set) == "function" then
					passTable.set(passValue, r,g,b,a)
				else
					if type(handler[passTable.set]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.set))
					end
					handler[passTable.set](handler, passValue, r,g,b,a)
				end
			else
				if type(options.set) == "function" then
					options.set(r,g,b,a)
				else
					if type(handler[options.set]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.set))
					end
					handler[options.set](handler, r,g,b,a)
				end
			end
			
			local r,g,b,a
			if passTable then
				if type(passTable.get) == "function" then
					r,g,b,a = passTable.get(passValue)
				else
					if type(handler[passTable.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
					end
					r,g,b,a = handler[passTable.get](handler, passValue)
				end
			else
				if type(options.get) == "function" then
					r,g,b,a = options.get()
				else
					if type(handler[options.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.get))
					end
					r,g,b,a = handler[options.get](handler)
				end
			end
			
			local s
			if type(r) == "number" and type(g) == "number" and type(b) == "number" then
				if options.hasAlpha and type(a) == "number" then
					s = string.format("|c%02x%02x%02x%02x%02x%02x%02x%02x|r", a*255, r*255, g*255, b*255, r*255, g*255, b*255, a*255)
				else
					s = string.format("|cff%02x%02x%02x%02x%02x%02x|r", r*255, g*255, b*255, r*255, g*255, b*255)
				end
			else
				s = NONE
			end
			print(string.format(options.message or IS_NOW_SET_TO, tostring(options.cmdName or options.name), s), realOptions.cmdName or realOptions.name or self)
		else
			local r,g,b,a
			if passTable then
				if type(passTable.get) == "function" then
					r,g,b,a = passTable.get(passValue)
				else
					if type(handler[passTable.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
					end
					r,g,b,a = handler[passTable.get](handler, passValue)
				end
			else
				if type(options.get) == "function" then
					r,g,b,a = options.get()
				else
					if type(handler[options.get]) ~= "function" then
						AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, options.get))
					end
					r,g,b,a = handler[options.get](handler)
				end
			end
			
			if not colorTable then
				colorTable = {}
				local t = colorTable
				
				if ColorPickerOkayButton then
					local ColorPickerOkayButton_OnClick = ColorPickerOkayButton:GetScript("OnClick")
					ColorPickerOkayButton:SetScript("OnClick", function()
						if ColorPickerOkayButton_OnClick then
							ColorPickerOkayButton_OnClick()
						end
						if t.active then
							ColorPickerFrame.cancelFunc = nil
							ColorPickerFrame.func = nil
							ColorPickerFrame.opacityFunc = nil
							local r,g,b,a
							if t.passValue then
								if type(t.get) == "function" then
									r,g,b,a = t.get(t.passValue)
								else
									if type(t.handler[t.get]) ~= "function" then
										AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, t.get))
									end
									r,g,b,a = t.handler[t.get](t.handler, t.passValue)
								end
							else
								if type(t.get) == "function" then
									r,g,b,a = t.get()
								else
									if type(t.handler[t.get]) ~= "function" then
										AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, t.get))
									end
									r,g,b,a = t.handler[t.get](t.handler)
								end
							end
							if r ~= t.r or g ~= t.g or b ~= t.b or (t.hasAlpha and a ~= t.a) then
								local s
								if type(r) == "number" and type(g) == "number" and type(b) == "number" then
									if t.hasAlpha and type(a) == "number" then
										s = string.format("|c%02x%02x%02x%02x%02x%02x%02x%02x|r", a*255, r*255, g*255, b*255, r*255, g*255, b*255, a*255)
									else
										s = string.format("|cff%02x%02x%02x%02x%02x%02x|r", r*255, g*255, b*255, r*255, g*255, b*255)
									end
								else
									s = NONE
								end
								print(string.format(t.message, tostring(t.name), s), t.realOptions.cmdName or t.realOptions.name or self)
							end
							for k,v in pairs(t) do
								t[k] = nil
							end
						end
					end)
				end
			else
				for k,v in pairs(colorTable) do
					colorTable[k] = nil
				end
			end
			
			if type(r) ~= "number" or type(g) ~= "number" or type(b) ~= "number" then
				r,g,b = 1, 1, 1
			end
			if type(a) ~= "number" then
				a = 1
			end
			local t = colorTable
			t.r = r
			t.g = g
			t.b = b
			if hasAlpha then
				t.a = a
			end
			t.realOptions = realOptions
			t.hasAlpha = options.hasAlpha
			t.handler = handler
			t.set = passTable and passTable.set or options.set
			t.get = passTable and passTable.get or options.get
			t.name = options.cmdName or options.name
			t.message = options.message or IS_NOW_SET_TO
			t.passValue = passValue
			t.active = true
			
			if not colorFunc then
				colorFunc = function()
					local r,g,b = ColorPickerFrame:GetColorRGB()
					if t.hasAlpha then
						local a = 1 - OpacitySliderFrame:GetValue()
						if type(t.set) == "function" then
							if t.passValue then
								t.set(t.passValue, r,g,b,a)
							else
								t.set(r,g,b,a)
							end
						else
							if type(t.handler[t.set]) ~= "function" then
								AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, t.set))
							end
							if t.passValue then
								t.handler[t.set](t.handler, t.passValue, r,g,b,a)
							else
								t.handler[t.set](t.handler, r,g,b,a)
							end
						end
					else
						if type(t.set) == "function" then
							if t.passValue then
								t.set(t.passValue, r,g,b)
							else
								t.set(r,g,b)
							end
						else
							if type(t.handler[t.set]) ~= "function" then
								AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, t.set))
							end
							if t.passValue then
								t.handler[t.set](t.handler, t.passValue, r,g,b)
							else
								t.handler[t.set](t.handler, r,g,b)
							end
						end
					end
				end
			end
			
			ColorPickerFrame.func = colorFunc
			ColorPickerFrame.hasOpacity = options.hasAlpha
			if options.hasAlpha then
				ColorPickerFrame.opacityFunc = ColorPickerFrame.func
				ColorPickerFrame.opacity = 1 - a
			end
			ColorPickerFrame:SetColorRGB(r,g,b)
			
			if not colorCancelFunc then
				colorCancelFunc = function()
					if t.hasAlpha then
						if type(t.set) == "function" then
							if t.passValue then
								t.set(t.passValue, t.r,t.g,t.b,t.a)
							else
								t.set(t.r,t.g,t.b,t.a)
							end
						else
							if type(t.handler[t.get]) ~= "function" then
								AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, t.get))
							end
							if t.passValue then
								t.handler[t.set](t.handler, t.passValue, t.r,t.g,t.b,t.a)
							else
								t.handler[t.set](t.handler, t.r,t.g,t.b,t.a)
							end
						end
					else
						if type(t.set) == "function" then
							if t.passValue then
								t.set(t.passValue, t.r,t.g,t.b)
							else
								t.set(t.r,t.g,t.b)
							end
						else
							if type(t.handler[t.set]) ~= "function" then
								AceConsole:error("%s: %s", handler, string.format(OPTION_HANDLER_NOT_FOUND, t.set))
							end
							if t.passValue then
								t.handler[t.set](t.handler, t.passValue, t.r,t.g,t.b)
							else
								t.handler[t.set](t.handler, t.r,t.g,t.b)
							end
						end
					end
					for k,v in pairs(t) do
						t[k] = nil
					end
					ColorPickerFrame.cancelFunc = nil
					ColorPickerFrame.func = nil
					ColorPickerFrame.opacityFunc = nil
				end
			end
			
			ColorPickerFrame.cancelFunc = colorCancelFunc
			
			ShowUIPanel(ColorPickerFrame)
		end
		return
	elseif kind == "group" then
		if #args == 0 then
			printUsage(self, handler, realOptions, options, path, args)
		else
			-- invalid argument
			print(string.format(options.error or IS_NOT_A_VALID_OPTION_FOR, args[1], path), realOptions.cmdName or realOptions.name or self)
		end
		return
	end
	this = _G_this
	if Dewdrop then
		Dewdrop:Refresh(1)
		Dewdrop:Refresh(2)
		Dewdrop:Refresh(3)
		Dewdrop:Refresh(4)
		Dewdrop:Refresh(5)
	end
end

local external
function AceConsole:RegisterChatCommand(slashCommands, options, name)
	if type(slashCommands) ~= "table" and slashCommands ~= false then
		AceConsole:error("Bad argument #2 to `RegisterChatCommand' (expected table, got %s)", type(slashCommands))
	end
	if not slashCommands and type(name) ~= "string" then
		AceConsole:error("Bad argument #4 to `RegisterChatCommand' (expected string, got %s)", type(name))
	end
	if type(options) ~= "table" and type(options) ~= "function" and options ~= nil then
		AceConsole:error("Bad argument #3 to `RegisterChatCommand' (expected table, function, or nil, got %s)", type(options))
	end
	if name then
		if type(name) ~= "string" then
			AceConsole:error("Bad argument #4 to `RegisterChatCommand' (expected string or nil, got %s)", type(name))
		elseif not name:find("^%w+$") or name:upper() ~= name or name:len() == 0 then
			AceConsole:error("Argument #4 must be an uppercase, letters-only string with at least 1 character")
		end
	end
	if slashCommands then
		if #slashCommands == 0 then
			AceConsole:error("Argument #2 to `RegisterChatCommand' must include at least one string")
		end
		
		for k,v in pairs(slashCommands) do
			if type(k) ~= "number" then
				AceConsole:error("All keys in argument #2 to `RegisterChatCommand' must be numbers")
			end
			if type(v) ~= "string" then
				AceConsole:error("All values in argument #2 to `RegisterChatCommand' must be strings")
			elseif not v:find("^/[A-Za-z][A-Za-z0-9_]*$") then
				AceConsole:error("All values in argument #2 to `RegisterChatCommand' must be in the form of \"/word\"")
			end
		end
	end
	
	if not options then
		options = {
			type = 'group',
			args = {},
			handler = self
		}
	end
	
	if type(options) == "table" then
		local err, position = validateOptions(options)
		if err then
			if position then
				AceConsole:error(position .. ": " .. err)
			else
				AceConsole:error(err)
			end
		end
		
		if not options.handler then
			options.handler = self
		end
		
		if options.handler == self and options.type:lower() == "group" and self.class then
			AceConsole:InjectAceOptionsTable(self, options)
		end
	end
	
	local chat
	if slashCommands then
		chat = slashCommands[1]
	else
		chat = _G["SLASH_"..name..1]
	end
	
	local handler
	if type(options) == "function" then
		handler = options
		for k,v in pairs(_G) do
			if handler == v then
				local k = k
				handler = function(msg)
					return _G[k](msg)
				end
			end
		end
	else
		function handler(msg)
			handlerFunc(self, chat, msg, options)
		end
	end
	
	if not _G.SlashCmdList then
		_G.SlashCmdList = {}
	end
	
	if not name then
		local A = ('A'):byte()
		repeat
			name = string.char(math.random(26) + A - 1) .. string.char(math.random(26) + A - 1) .. string.char(math.random(26) + A - 1) .. string.char(math.random(26) + A - 1) .. string.char(math.random(26) + A - 1) .. string.char(math.random(26) + A - 1) .. string.char(math.random(26) + A - 1) .. string.char(math.random(26) + A - 1)
		until not _G.SlashCmdList[name]
	end
	
	if slashCommands then
		if _G.SlashCmdList[name] then
			local i = 0
			while true do
				i = i + 1
				if _G["SLASH_"..name..i] then
					_G["SLASH_"..name..i] = nil
				else
					break
				end
			end
		end
		
		local i = 0
		for _,command in ipairs(slashCommands) do
			i = i + 1
			_G["SLASH_"..name..i] = command
			if command:lower() ~= command then
				i = i + 1
				_G["SLASH_"..name..i] = command:lower()
			end
		end
	end
	_G.SlashCmdList[name] = handler
	if self ~= AceConsole and self.slashCommand == nil then
		self.slashCommand = chat
	end
	
	if not AceEvent and AceLibrary:HasInstance("AceEvent-2.0") then
		external(AceConsole, "AceEvent-2.0", AceLibrary("AceEvent-2.0"))
	end
	if AceEvent then
		if not AceConsole.nextAddon then
			AceConsole.nextAddon = {}
		end
		if type(options) == "table" then
			AceConsole.nextAddon[self] = options
			if not self.playerLogin then
				AceConsole:RegisterEvent("PLAYER_LOGIN", "PLAYER_LOGIN", true)
			end
		end
	end
	
	AceConsole.registry[name] = options
end

function AceConsole:InjectAceOptionsTable(handler, options)
	self:argCheck(handler, 2, "table")
	self:argCheck(options, 3, "table")
	if options.type:lower() ~= "group" then
		self:error('Cannot inject into options table argument #3 if its type is not "group"')
	end
	if options.handler ~= nil and options.handler ~= handler then
		self:error("Cannot inject into options table argument #3 if it has a different handler than argument #2")
	end
	options.handler = handler
	local class = handler.class
	if not class then
		self:error("Cannot retrieve AceOptions tables from a non-object argument #2")
	end
	while class and class ~= AceOO.Class do
		if type(class.GetAceOptionsDataTable) == "function" then
			local t = class:GetAceOptionsDataTable(handler)
			for k,v in pairs(t) do
				if type(options.args) ~= "table" then
					options.args = {}
				end
				if options.args[k] == nil then
					options.args[k] = v
				end
			end
		end
		local mixins = class.mixins
		if mixins then
			for mixin in pairs(mixins) do
				if type(mixin.GetAceOptionsDataTable) == "function" then
					local t = mixin:GetAceOptionsDataTable(handler)
					for k,v in pairs(t) do
						if type(options.args) ~= "table" then
							options.args = {}
						end
						if options.args[k] == nil then
							options.args[k] = v
						end
					end
				end
			end
		end
		class = class.super
	end
	return options
end

function AceConsole:PLAYER_LOGIN()
	self.playerLogin = true
	for addon, options in pairs(self.nextAddon) do
		local err, position = validateOptionsMethods(addon, options)
		if err then
			if position then
				geterrorhandler()(tostring(addon) .. ": AceConsole: " .. position .. ": " .. err)
			else
				geterrorhandler()(tostring(addon) .. ": AceConsole: " .. err)
			end
		end
		self.nextAddon[addon] = nil
	end
end

function AceConsole:TabCompleteInfo(cmdpath)
	local cmd =  cmdpath:match("(/%S+)")
	if not cmd then
		return
	end
	local path = cmdpath:sub(cmd:len() + 2)
	for name in pairs(SlashCmdList) do --global
		if AceConsole.registry[name] then
			local i = 0
			while true do
				i = i + 1
				local scmd = _G["SLASH_"..name..i]
				if not scmd then break end
				if cmd == scmd then
					return name, cmd, path
				end
			end
		end
	end
end

function external(self, major, instance)
	if major == "AceEvent-2.0" then
		if not AceEvent then
			AceEvent = instance
			
			AceEvent:embed(self)
		end
	elseif major == "AceTab-2.0" then
		instance:RegisterTabCompletion("AceConsole", "%/.*", function(t, cmdpath, pos)
			local ac = AceLibrary("AceConsole-2.0")
			local name, cmd, path = ac:TabCompleteInfo(cmdpath:sub(1, pos))

			if not ac.registry[name] then
				return false
			else
				local validArgs = findTableLevel(ac, ac.registry[name], cmd, path or "")
				if validArgs.args then
					for arg in pairs(validArgs.args) do
						table.insert(t, arg)
					end
				end
			end
		end, function(u, matches, gcs, cmdpath)
			local ac = AceLibrary("AceConsole-2.0")
			local name, cmd, path = ac:TabCompleteInfo(cmdpath)
			if ac.registry[name] then
				local validArgs, path2, argwork = findTableLevel(ac, ac.registry[name], cmd, path)
				printUsage(ac, validArgs.handler, ac.registry[name], validArgs, path2, argwork, not gcs or gcs ~= "", gcs)
			end
		end)
	elseif major == "Dewdrop-2.0" then
		Dewdrop = instance
	end
end

local function activate(self, oldLib, oldDeactivate)
	AceConsole = self
	
	if oldLib then
		self.registry = oldLib.registry
		self.nextAddon = oldLib.nextAddon
	end
	
	if not self.registry then
		self.registry = {}
	else
		for name,options in pairs(self.registry) do
			self:RegisterChatCommand(false, options, name)
		end
	end
	
	self:RegisterChatCommand({ "/reload", "/rl", "/reloadui" }, ReloadUI, "RELOAD")
	
	self:RegisterChatCommand({ "/print" }, function(text)
		local f, err = loadstring("AceLibrary('AceConsole-2.0'):PrintComma(" .. text .. ")")
		if not f then
			self:Print("|cffff0000Error:|r", err)
		else
			f()
		end
	end, "PRINT")
	
	self:activate(oldLib, oldDeactivate)
	
	if oldDeactivate then
		oldDeactivate(oldLib)
	end
end

AceLibrary:Register(AceConsole, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)