Quantcast

* Updated to Dongle-0.3.0-Alpha

James Whitehead II [12-15-06 - 22:37]
* Updated to Dongle-0.3.0-Alpha
Filename
Dongle.lua
diff --git a/Dongle.lua b/Dongle.lua
index e9f54b7..4590ba4 100644
--- a/Dongle.lua
+++ b/Dongle.lua
@@ -90,7 +90,7 @@ Begin Library Implementation
 ---------------------------------------------------------------------------]]

 local major = "Dongle"
-local minor = tonumber(select(3,string.find("$Revision: 70 $", "(%d+)")) or 1)
+local minor = tonumber(select(3,string.find("$Revision: 92 $", "(%d+)")) or 1)

 assert(DongleStub, string.format("%s requires DongleStub.", major))
 if not DongleStub:IsNewerVersion(major, minor) then return end
@@ -98,9 +98,8 @@ if not DongleStub:IsNewerVersion(major, minor) then return end
 Dongle = {}
 local methods = {
 	"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", "TriggerEvent",
-	"EnableDebug", "Print", "Debug",
-	"InitializeDB", "RegisterDBDefaults", "ResetDB",
-	"SetProfile", "CopyProfile", "ResetProfile", "DeleteProfile",
+	"EnableDebug", "Print", "PrintF", "Debug", "DebugF",
+	"InitializeDB",
 	"NewModule", "HasModule", "IterateModules",
 }

@@ -109,6 +108,8 @@ local lookup = {}
 local loadqueue = {}
 local loadorder = {}
 local events = {}
+local databases = {}
+local commands = {}

 local function assert(level,condition,message)
 	if not condition then
@@ -132,17 +133,16 @@ end

 local function safecall(func,...)
 	local success,err = pcall(func,...)
-	if not success then
+	if not success then
 		geterrorhandler()(err)
 	end
 end

-function Dongle:New(obj, name)
-	argcheck(obj, 2, "table", "string", "nil")
-	argcheck(name, 3, "string", "nil")
+function Dongle:New(name, obj)
+	argcheck(name, 2, "string")
+	argcheck(obj, 3, "table", "nil")

-	if not name and type(obj) == "string" then
-		name = obj
+	if not obj then
 		obj = {}
 	end

@@ -165,27 +165,29 @@ function Dongle:New(obj, name)
 	return obj,name
 end

-function Dongle:NewModule(obj, name)
+function Dongle:NewModule(name, obj)
 	local reg = lookup[self]
 	assert(3, reg, "You must call 'NewModule' from a registered Dongle.")
-	argcheck(obj, 2, "table", "string", "nil")
-	argcheck(name, 3, "string", "nil")
+	argcheck(name, 2, "string")
+	argcheck(obj, 3, "table", "nil")

-	obj,name = Dongle:New(obj, name)
+	obj,name = Dongle:New(name, obj)

 	if not reg.modules then reg.modules = {} end
+	reg.modules[obj] = obj
+	reg.modules[name] = obj
 	table.insert(reg.modules, name)
 	table.sort(reg.modules)

 	return obj,name
 end

-function Dongle:HasModule(name)
+function Dongle:HasModule(module)
 	local reg = lookup[self]
 	assert(3, reg, "You must call 'HasModule' from a registered Dongle.")
-	argcheck(name, 2, "string")
+	argcheck(module, 2, "string", "table")

-	return lookup[name]
+	return reg.modules[module]
 end

 local EMPTY_TABLE = {}
@@ -228,7 +230,7 @@ function Dongle:TriggerEvent(event, ...)
 	if eventTbl then
 		for obj,func in pairs(eventTbl) do
 			if type(func) == "string" then
-				if type(obj[func]) then
+				if type(obj[func]) == "function" then
 					safecall(obj[func], obj, event, ...)
 				end
 			else
@@ -243,7 +245,7 @@ function Dongle:OnEvent(frame, event, ...)
 	if eventTbl then
 		for obj,func in pairs(eventTbl) do
 			if type(func) == "string" then
-				if type(obj[func]) then
+				if type(obj[func]) == "function" then
 					obj[func](obj, event, ...)
 				end
 			else
@@ -288,6 +290,10 @@ function Dongle:UnregisterAllEvents()

 	for event,tbl in pairs(events) do
 		tbl[self] = nil
+		if not next(tbl) then
+			events[event] = nil
+			frame:UnregisterEvent(event)
+		end
 	end
 end

@@ -319,41 +325,80 @@ function Dongle:EnableDebug(level)
 end

 do
+	local function argsToStrings(a1, ...)
+		if select("#", ...) > 0 then
+			return tostring(a1), argsToStrings(...)
+		else
+			return tostring(a1)
+		end
+	end
+
 	local function printHelp(obj, method, msg, ...)
 		local reg = lookup[obj]
 		assert(4, reg, "You must call '"..method.."' from a registered Dongle.")

 		local name = reg.name
-		local msg = string.format("|cFF33FF99%s|r: %s", name, msg)
+		msg = "|cFF33FF99"..name.."|r: "..tostring(msg)
+		if select("#", ...) > 0 then
+			msg = string.join(", ", msg, argsToStrings(...))
+		end
+
+		ChatFrame1:AddMessage(msg)
+	end
+
+	local function printFHelp(obj, method, msg, ...)
+		local reg = lookup[obj]
+		assert(4, reg, "You must call '"..method.."' from a registered Dongle.")

-		local success,txt = pcall(string.format, msg, ...)
+		local name = reg.name
+		local success,txt = pcall(string.format, "|cFF33FF99%s|r: "..msg, name, ...)
 		if success then
-			ChatFrame1:AddMessage(string.format(txt, ...))
+			ChatFrame1:AddMessage(txt)
 		else
 			error(string.gsub(txt, "'%?'", string.format("'%s'", method)), 3)
 		end
 	end

-	function Dongle:Print(msg, ...)
-		return printHelp(self, "Print", msg, ...)
+	function Dongle:Print(...)
+		return printHelp(self, "Print", ...)
+	end
+
+	function Dongle:PrintF(...)
+		return printFHelp(self, "PrintF", ...)
 	end

-	function Dongle:Debug(level, msg, ...)
+	function Dongle:Debug(level, ...)
 		local reg = lookup[self]
 		assert(3, reg, "You must call 'Debug' from a registered Dongle.")
 		argcheck(level, 2, "number")
-
+
 		if reg.debugLevel and level >= reg.debugLevel then
-			printHelp(self, "Debug", msg, ...)
+			printHelp(self, "Debug", ...)
+		end
+	end
+
+	function Dongle:DebugF(level, ...)
+		local reg = lookup[self]
+		assert(3, reg, "You must call 'DebugF' from a registered Dongle.")
+		argcheck(level, 2, "number")
+
+		if reg.debugLevel and level >= reg.debugLevel then
+			printFHelp(self, "DebugF", ...)
 		end
 	end
 end

-function Dongle:InitializeDB(name, defaults)
+local dbMethods = {
+	"RegisterDefaults", "SetProfile", "GetProfiles", "DeleteProfile", "CopyProfile",
+	"ResetProfile", "ResetDB",
+}
+
+function Dongle:InitializeDB(name, defaults, defaultProfile)
 	local reg = lookup[self]
 	assert(3, reg, "You must call 'InitializeDB' from a registered Dongle.")
 	argcheck(name, 2, "string")
 	argcheck(defaults, 3, "table", "nil")
+	argcheck(defaultProfile, 4, "string", "nil")

 	local sv = getglobal(name)

@@ -384,6 +429,7 @@ function Dongle:InitializeDB(name, defaults)
 	if not sv.faction then sv.faction = {} end
 	if not sv.global then sv.global = {} end
 	if not sv.profiles then sv.profiles = {} end
+	if not sv.profileKeys then sv.profileKeys = {} end

 	-- Initialize this characters profiles
 	if not sv.char[char] then sv.char[char] = {} end
@@ -392,8 +438,8 @@ function Dongle:InitializeDB(name, defaults)
 	if not sv.faction[faction] then sv.faction[faction] = {} end

 	-- Try to get the profile selected from the char db
-	local profileKey = sv.char[char].profileKey or char
-	sv.char[char].profileKey = profileKey
+	local profileKey = sv.profileKeys[char] or defaultProfile or char
+	sv.profileKeys[char] = profileKey

 	if not sv.profiles[profileKey] then sv.profiles[profileKey] = {} end

@@ -407,40 +453,47 @@ function Dongle:InitializeDB(name, defaults)
 		["profiles"] = sv.profiles,
 	}

-	local reg = lookup[self]
-	reg.sv = sv
-	reg.sv_name = name
-	reg.db = db
-	reg.db_char = char
-	reg.db_realm = realm
-	reg.db_class = class
-	reg.db_faction = faction
-	reg.db_profileKey = profileKey
+	-- Copy methods locally
+	for idx,method in pairs(dbMethods) do
+		db[method] = Dongle[method]
+	end
+
+	-- Set some properties in the object we're returning
+	db.sv = sv
+	db.sv_name = name
+	db.profileKey = profileKey
+	db.parent = reg.name
+	db.charKey = char
+	db.realmKey = realm
+	db.classKey = class
+	db.factionKey = faction
+
+	databases[db] = true

 	if defaults then
-		self:RegisterDBDefaults(db, defaults)
+		db:RegisterDefaults(defaults)
 	end

 	return db
 end

-local function copyDefaults(dest, src)
+local function copyDefaults(dest, src, force)
 	for k,v in pairs(src) do
 		if type(v) == "table" then
 			if not dest[k] then dest[k] = {} end
-			copyDefaults(dest[k], v)
+			copyDefaults(dest[k], v, force)
 		else
-			dest[k] = v
+			if not dest[k] or force then
+				dest[k] = v
+			end
 		end
 	end
 end

-function Dongle:RegisterDBDefaults(db, defaults)
-	local reg = lookup[self]
-	assert(3, reg, "You must call 'RegisterDBDefaults' from a registered Dongle.")
-	argcheck(db, 2, "table")
-	argcheck(defaults, 3, "table")
-	assert(3, reg.db, "You cannot call \"RegisterDBDefaults\" before calling \"InitializeDB\".")
+-- This function operates on a Dongle DB object
+function Dongle.RegisterDefaults(db, defaults)
+	assert(3, databases[db], "You must call 'RegisterDefaults' from a Dongle database object.")
+	argcheck(defaults, 2, "table")

 	if defaults.char then copyDefaults(db.char, defaults.char) end
 	if defaults.realm then copyDefaults(db.realm, defaults.realm) end
@@ -449,7 +502,7 @@ function Dongle:RegisterDBDefaults(db, defaults)
 	if defaults.global then copyDefaults(db.global, defaults.global) end
 	if defaults.profile then copyDefaults(db.profile, defaults.profile) end

-	reg.dbDefaults = defaults
+	db.defaults = defaults
 end

 local function removeDefaults(db, defaults)
@@ -469,10 +522,9 @@ local function removeDefaults(db, defaults)
 end

 function Dongle:ClearDBDefaults()
-	for name,obj in pairs(registry) do
-		local db = obj.db
-		local defaults = obj.dbDefaults
-		local sv = obj.sv
+	for db in pairs(databases) do
+		local defaults = db.defaults
+		local sv = db.sv

 		if db and defaults then
 			if defaults.char then removeDefaults(db.char, defaults.char) end
@@ -480,152 +532,154 @@ function Dongle:ClearDBDefaults()
 			if defaults.class then removeDefaults(db.class, defaults.class) end
 			if defaults.faction then removeDefaults(db.faction, defaults.faction) end
 			if defaults.global then removeDefaults(db.global, defaults.global) end
-			if defaults.profile then
+			if defaults.profile then
 				for k,v in pairs(sv.profiles) do
 					removeDefaults(sv.profiles[k], defaults.profile)
 				end
 			end

 			-- Remove any blank "profiles"
-			if not next(db.char) then sv.char[obj.db_char] = nil end
-			if not next(db.realm) then sv.realm[obj.db_realm] = nil end
-			if not next(db.class) then sv.class[obj.db_class] = nil end
-			if not next(db.faction) then sv.faction[obj.db_faction] = nil end
+			if not next(db.char) then sv.char[db.charKey] = nil end
+			if not next(db.realm) then sv.realm[db.realmKey] = nil end
+			if not next(db.class) then sv.class[db.classKey] = nil end
+			if not next(db.faction) then sv.faction[db.factionKey] = nil end
 			if not next(db.global) then sv.global = nil end
 		end
 	end
 end

-function Dongle:SetProfile(name)
-	local reg = lookup[self]
-	assert(3, reg, "You must call 'SetProfile' from a registered Dongle.")
+function Dongle.SetProfile(db, name)
+	assert(3, databases[db], "You must call 'SetProfile' from a Dongle database object.")
 	argcheck(name, 2, "string")
-	assert(3, reg.db, "You cannot call \"SetProfile\" before calling \"InitializeDB\".")

-	local old = reg.sv.profiles[reg.db_profileKey]
+	local sv = db.sv
+	local old = sv.profiles[db.profileKey]
+	local new = sv.profiles[name]

-	local new = reg.sv.profiles[name]
 	if not new then
-		reg.sv.profiles[name] = {}
-		new = reg.sv.profiles[name]
+		sv.profiles[name] = {}
+		new = sv.profiles[name]
 	end

-	if reg.dbDefaults and reg.dbDefaults.profile then
+	if db.defaults and db.defaults.profile then
 		-- Remove the defaults from the old profile
-		removeDefaults(old, reg.dbDefaults.profile)
+		removeDefaults(old, db.defaults.profile)

 		-- Inject the defaults into the new profile
-		copyDefaults(new, reg.dbDefaults.profile)
+		copyDefaults(new, db.defaults.profile)
 	end

-	reg.db.profile = new
+	db.profile = new

-	-- Save this new profile name in db.char
-	reg.db.char.profileKey = name
+	-- Save this new profile name
+	sv.profileKeys[db.charKey] = name
+    db.profileKey = name

-	self:TriggerEvent("DONGLE_PROFILE_CHANGED", reg.name, name)
+	-- FIRE: DONGLE_PROFILE_CHANGED, "DongleName", "SVName", "ProfileName"
+	local parent = lookup[db.parent].obj
+	parent:TriggerEvent("DONGLE_PROFILE_CHANGED", db.parent, db.sv_name, name)
 end

-function Dongle:DeleteProfile(name)
-	local reg = lookup[self]
-	assert(3, reg, "You must call 'DeleteProfile' from a registered Dongle.")
+function Dongle.GetProfiles(db, t)
+	assert(3, databases[db], "You must call 'GetProfiles' from a Dongle database object.")
+	argcheck(t, 2, "table", "nil")
+
+	t = t or {}
+	local i = 1
+	for profileKey in pairs(db.profiles) do
+		t[i] = profileKey
+		i = i + 1
+	end
+	return t, i - 1
+end
+
+function Dongle.DeleteProfile(db, name)
+	assert(3, databases[db], "You must call 'DeleteProfile' from a Dongle database object.")
 	argcheck(name, 2, "string")
-	assert(3, reg.db, "You cannot call \"DeleteProfile\" before calling \"InitializeDB\".")

-	if reg.db.char.profileKey == name then
+	if db.profileKey == name then
 		error("You cannot delete your active profile.  Change profiles, then attempt to delete.", 2)
 	end

-	reg.sv.profiles[name] = nil
-	self:TriggerEvent("DONGLE_PROFILE_DELETED", reg.name, name)
+	db.sv.profiles[name] = nil
+	local parent = lookup[db.parent].obj
+	parent:TriggerEvent("DONGLE_PROFILE_DELETED", db.parent, db.sv_name, name)
 end

-function Dongle:CopyProfile(name)
-	local reg = lookup[self]
-	assert(3, reg, "You must call 'CopyProfile' from a registered Dongle.")
+function Dongle.CopyProfile(db, name)
+	assert(3, databases[db], "You must call 'CopyProfile' from a Dongle database object.")
 	argcheck(name, 2, "string")
-	assert(3, reg.db, "You cannot call \"CopyProfile\" before calling \"InitializeDB\".")

-	assert(3, reg.db.char.profileKey ~= name, "Source/Destination profile cannot be the same profile")
-	assert(3, type(reg.sv.profiles[name]) == "table", "Profile \""..name.."\" doesn't exist.")
+	assert(3, db.profileKey ~= name, "Source/Destination profile cannot be the same profile")
+	assert(3, type(db.sv.profiles[name]) == "table", "Profile \""..name.."\" doesn't exist.")

-	local profile = reg.db.profile
-	local source = reg.sv.profiles[name]
+	local profile = db.profile
+	local source = db.sv.profiles[name]

 	-- Don't do a destructive copy, just do what we're told
-	copyDefaults(profile, source)
-	self:TriggerEvent("DONGLE_PROFILE_COPIED", reg.name, name, reg.db.char.profileKey)
+	copyDefaults(profile, source, true)
+	-- FIRE: DONGLE_PROFILE_COPIED, "DongleName", "SVName", "SourceProfile", "DestProfile"
+	local parent = lookup[db.parent].obj
+	parent:TriggerEvent("DONGLE_PROFILE_COPIED", db.parent, db.sv_name, name, db.profileKey)
 end

-function Dongle:ResetProfile()
-	local reg = lookup[self]
-	assert(3, reg, "You must call 'ResetProfile' from a registered Dongle.")
-	assert(3, reg.db, "You cannot call \"ResetProfile\" before calling \"InitializeDB\".")
+function Dongle.ResetProfile(db)
+	assert(3, databases[db], "You must call 'ResetProfile' from a Dongle database object.")

-	local profile = reg.db.profile
+	local profile = db.profile

 	for k,v in pairs(profile) do
 		profile[k] = nil
 	end
-	if reg.dbDefaults and reg.dbDefaults.profile then
-		copyDefaults(profile, reg.dbDefaults.profile)
+	if db.defaults and db.defaults.profile then
+		copyDefaults(profile, db.defaults.profile)
 	end
-	self:TriggerEvent("DONGLE_PROFILE_RESET", reg.name, name)
+	-- FIRE: DONGLE_PROFILE_RESET, "DongleName", "SVName", "ProfileName"
+	local parent = lookup[db.parent].obj
+	parent:TriggerEvent("DONGLE_PROFILE_RESET", db.parent, db.sv_name, db.profileKey)
 end

-function Dongle:ResetDB()
-	local reg = lookup[self]
-	assert(3, reg, "You must call 'ResetDB' from a registered Dongle.")
-	assert(3, reg.db, "You cannot call \"ResetDB\" before calling \"InitializeDB\".")

-	local sv = reg.sv
+function Dongle.ResetDB(db, defaultProfile)
+	assert(3, databases[db], "You must call 'ResetDB' from a Dongle database object.")
+    argcheck(defaultProfile, 2, "nil", "string")
+
+	local sv = db.sv
 	for k,v in pairs(sv) do
 		sv[k] = nil
 	end
-
-	local db = self:InitializeDB(reg.sv_name, reg.dbDefaults)
-	self:SetProfile(reg.db.char.profileKey)
-	self:TriggerEvent("DONGLE_DATABASE_RESET", reg.name)
-	return db
-end

--- Set up a basic slash command for /dongle and /reload
-SLASH_RELOAD1 = "/reload"
-SLASH_RELOAD2 = "/rl"
-SlashCmdList["RELOAD"] = ReloadUI
+	local parent = lookup[db.parent].obj

-SLASH_DONGLE1 = "/dongle"
+	local newdb = parent:InitializeDB(db.sv_name, db.defaults, defaultProfile)
+	newdb:SetProfile(newdb.profileKey)
+	local parent = lookup[db.parent].obj
+	parent:TriggerEvent("DONGLE_DATABASE_RESET", newdb.parent, newdb.sv_name, newdb.profileKey)

-SlashCmdList["DONGLE"] = function(msg)
-	local s,e,cmd,args = string.find(msg, "([^%s]+)%s*(.*)")
-	if not cmd then return end
+	-- Remove the old database from the lookup table
+	databases[db] = nil
+	return newdb
+end

-	cmd = string.lower(cmd)
+function Dongle:RegisterSlashCommand(command, prefix, pattern, validator)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'RegisterSlashCommand' from a registered Dongle.")
+	argcheck(prefix, 2, "string")
+	argcheck(pattern, 3, "string", "nil")
+	argcheck(validator, 4, "function", "nil")

-	if cmd == "enable" then
-		local name,title,notes,enabled = GetAddOnInfo(args)
-		if enabled then
-			Dongle:Print("'%s' is already enabled.", args)
-		elseif not name then
-			Dongle:Print("'%s' is not a valid addon.", args)
-		elseif args then
-			EnableAddOn(args)
-			Dongle:Print("Enabled AddOn '%s'", args)
-		end
-	elseif cmd == "disable" then
-		local name,title,notes,enabled = GetAddOnInfo(args)
-		if not name then
-			Dongle:Print("'%s' is not a valid addon.", args)
-		elseif not enabled then
-			Dongle:Print("'%s' is already disabled.", args)
-		elseif args then
-			DisableAddOn(args)
-			Dongle:Print("Disabled AddOn '%s'", args)
-		end
+	if not reg.cmd then
+		reg.cmd = {}
 	end
-end
+	reg.cmd[prefix] = {
+		["pattern"] = pattern,
+		["validator"] = validator,
+	}
+
+	-- Register the slash command here

---]]
+
+end

 --[[-------------------------------------------------------------------------
   Begin DongleStub required functions and registration
@@ -635,17 +689,21 @@ function Dongle:GetVersion() return major,minor end

 function Dongle:Activate(old)
 	if old then
-		self.registry = old.registry
-		self.lookup = old.lookup
-		self.loadqueue = old.loadqueue
-		self.loadorder = old.loadorder
-		self.events = old.events
+		self.registry = old.registry or registry
+		self.lookup = old.lookup or lookup
+		self.loadqueue = old.loadqueue or loadqueue
+		self.loadorder = old.loadorder or loadorder
+		self.events = old.events or events
+		self.databases = old.databases or databases
+		self.commands = old.commands or commands

 		registry = self.registry
 		lookup = self.lookup
 		loadqueue = self.loadqueue
 		loadorder = self.loadorder
 		events = self.events
+		databases = self.databases
+		commands = self.commands

 		frame = old.frame
 		self.registry[major].obj = self
@@ -655,6 +713,8 @@ function Dongle:Activate(old)
 		self.loadqueue = loadqueue
 		self.loadorder = loadorder
 		self.events = events
+		self.databases = databases
+		self.commands = commands

 		local reg = {obj = self, name = "Dongle"}
 		registry[major] = reg
@@ -682,6 +742,13 @@ function Dongle:Activate(old)
 			obj[k] = self[v]
 		end
 	end
+
+	-- Convert all database methods
+	for db in pairs(databases) do
+		for idx,method in ipairs(dbMethods) do
+			db[method] = self[method]
+		end
+	end
 end

 function Dongle:Deactivate(new)