<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>Pointything/modules/Logging.py</filename>
    </added>
    <added>
      <filename>Pointything/modules/Markov.py</filename>
    </added>
    <added>
      <filename>Pointything/modules/Test.py</filename>
    </added>
    <added>
      <filename>Pointything/modules/db.py</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+r&quot;&quot;&quot;Various constraints for use in a sql schema&quot;&quot;&quot;
 class Constraint:
     def __init__(self, str):
         self.str = str
@@ -22,15 +23,18 @@ class PrimaryKey(Constraint):
         Constraint.__init__(self, &quot;primary key&quot;)
 
 class ForeignKey(Constraint):
+    &quot;&quot;&quot;A foreign key constraint. Requires an list of actions, which may be empty.&quot;&quot;&quot;
     def __init__(self, other, *actions):
         Constraint.__init__(self, &quot;references&quot;)
         self.other = other
         self.actions = actions
     
     def __repr__(self):
-        return &quot;references %s.%s %s&quot;%(self.other.table.name, self.other.name, reduce((lambda x,y:str(x)+&quot; &quot;+str(y)), self.actions))
+        return &quot;references %s(%s) %s&quot;%(self.other.table.name, self.other.name, reduce((lambda x,y:str(x)+&quot; &quot;+str(y)), self.actions))
 
 class ForeignKeyAction:
+    &quot;&quot;&quot;An action to perform on event, which may be one of &quot;UPDATE&quot;, &quot;DELETE&quot;,
+    or anything else supported by the underlying sql layer. The action is what to be done, such as &quot;CASCADE&quot; or &quot;SET DEFAULT&quot;.&quot;&quot;&quot;
     def __init__(self, event, action):
         self.event = event
         self.action = action</diff>
      <filename>Database/Constraints.py</filename>
    </modified>
    <modified>
      <diff>@@ -1,32 +1,50 @@
 # -*- coding: utf-8 -*-
+r&quot;&quot;&quot;Field types
+&quot;&quot;&quot;
+import Constraints
+
 class BaseField:
     def __init__(self, type, name):
         self.name = name
         self.constraints = []
         self.type = type
         self.table = None
+        self.foreignTables = []
     
-    def addConstraint(self, type):
-        self.constraints.append(type)
+    def addConstraint(self, c):
+        &quot;&quot;&quot;Applies a constraint to the field&quot;&quot;&quot;
+        if isinstance(c, Constraints.ForeignKey):
+                self.foreignTables.append(c.other.table)
+        self.constraints.append(c)
         return self
     
     def __repr__(self):
-        return &quot;%s %s %s&quot;%(self.name, self.type, reduce((lambda x,y:repr(x)+&quot; &quot;+repr(y)),self.constraints, &quot;&quot;))
+        constraints = &quot;&quot;
+        if len(self.constraints)&gt;0:
+            constraints = []
+            for c in self.constraints:
+                constraints.append(repr(c))
+        return &quot;%s %s %s&quot;%(self.name, self.type, &quot; &quot;.join(constraints))
     
     def __str__(self):
         return self.name
         
     def cast(self, data):
+        &quot;&quot;&quot;Converts data into the field's native format&quot;&quot;&quot;
         pass
 
 class Text(BaseField):
     def __init__(self, name):
         BaseField.__init__(self, 'text', name)
     def cast(self, data):
-        return int(data)
+        if data == None:
+            return &quot;&quot;
+        return str(data)
 
 class Integer(BaseField):
     def __init__(self, name):
         BaseField.__init__(self, 'integer', name)
     def cast(self, data):
+        if data == None:
+            return 0
         return int(data)
\ No newline at end of file</diff>
      <filename>Database/Fields.py</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+from Fields import BaseField
 class MatchBase:
     def __init__(self, operand, field, value):
         self.field = field
@@ -7,7 +8,32 @@ class MatchBase:
     
     def __str__(self):
         return str(self.field) + self.op + str(self.field.cast(self.value))
+    
+    def __repr__(self):
+        return str(self.field) + self.op + &quot;?&quot;
 
 class Equals(MatchBase):
+    RIGHT = 1
+    LEFT = 2
     def __init__(self, field, value):
-        MatchBase.__init__(self, '==', field, value)
\ No newline at end of file
+        if isinstance(field, BaseField):
+            self.direction = Equals.RIGHT
+            self.field = field
+            self.value = value
+        else:
+            self.direction = Equals.LEFT
+            self.field = value
+            self.value = field
+        MatchBase.__init__(self, '==', self.field, self.value)
+    
+    def __repr__(self):
+        if self.direction==Equals.RIGHT:
+            return str(self.field) + self.op + &quot;?&quot;
+        else:
+            return &quot;?&quot; + self.op + str(self.field)
+    
+    def __str__(self):
+        if self.direction==Equals.RIGHT:
+            return str(self.field) + self.op + str(self.field.cast(self.value))
+        else:
+            return str(self.field.cast(self.value)) + self.op + str(self.field)
\ No newline at end of file</diff>
      <filename>Database/Matches.py</filename>
    </modified>
    <modified>
      <diff>@@ -1,79 +1,221 @@
 # -*- coding: utf-8 -*-
+r&quot;&quot;&quot;An object-oriented database abstraction layer.
+
+The Database module is meant to provide an easy way to essentially
+write your database's schema right into your python code. It is best explained with an example:
+
+import Database
+class UserTable(Database.Table):
+    def __init__(self):
+        Database.Table.__init__(self, &quot;users&quot;)
+        self.addField(Database.Fields.Integer(&quot;user_id&quot;).addConstraint(Database.Constraints.PrimaryKey()).addConstraint(Database.Constraints.AutoIncrement()))
+        self.addField(Database.Fields.Text(&quot;login&quot;))
+        self.addField(Database.Fields.Text(&quot;password&quot;))
+
+print repr(UserTable())
+
+The output of this is valid SQL you can pass right into your favorite
+sql engine's query() function.
+
+More advanced SQL is possible as well:
+
+from Database import *
+
+class MarkovWordTable(Table):
+    def __init__(self):
+        Table.__init__(self, &quot;markov_words&quot;)
+        self.addField(
+            Fields.Integer(&quot;wid&quot;)
+            .addConstraint(Constraints.PrimaryKey())
+            .addConstraint(Constraints.AutoIncrement())
+            .addConstraint(Constraints.ForeignKey(MarkovLinkTable().field(&quot;from&quot;),
+                                                   (
+                                                        Constraints.ForeignKeyAction(&quot;delete&quot;, &quot;cascade&quot;)
+                                                   )
+                                                   ))
+            .addConstraint(Constraints.ForeignKey(MarkovLinkTable().field(&quot;to&quot;),
+                                                   (
+                                                        Constraints.ForeignKeyAction(&quot;delete&quot;, &quot;cascade&quot;)
+                                                   )
+                                                   ))
+            )
+        self.addField(Fields.Text(&quot;word&quot;))
+
+This creates a table named &quot;markov_words&quot; with two fields. The integer &quot;wid&quot; field
+is a primary key with autoincrement and two foreign keys. The keys exist in the Link table
+and are set to cascade on delete.
+
+Querying with a table object is remarkably simple as well:
+
+tbl = UserTable()
+u = tbl.select(tbl.fields(), (Database.Matches.Equals(tbl[&quot;user_id&quot;], id),))
+
+This essentially evaluates to &quot;SELECT * FROM users WHERE user_id == $id&quot;
+
+&quot;&quot;&quot;
+
 import sqlite3
 import logging
 import Fields, Constraints, Matches
 
 '''Using repr() on a database object returns valid SQL.'''
 
-instance = None
-
-def instance():
-    if (instance == None):
-        instance = Database()
-    return instance
-
 class Table:
+    &quot;&quot;&quot;Represents a table in a sql database&quot;&quot;&quot;
     def __init__(self, name):
         self.log = logging.getLogger(&quot;Pointything.Database.%s&quot;%name)
-        self.db = Database()
         self.name = name
         self._fields = {}
+        self.schemaReady = False
+        self.depends = []
+        self.db = Database()
+        self.db.addTable(self)
         
+    def create(self):
+        if self.schemaReady:
+            return
+        else:
+            for d in self.depends:
+                d.create()
+            self.db.createTable(self)
+            self.schemaReady = True
+    
+    def addDependency(self, other):
+        self.depends.append(other)
+    
     def addField(self, field):
+        &quot;&quot;&quot;Adds a field to the table's schema&quot;&quot;&quot;
         field.table = self
         self._fields.update({field.name: field})
+        for d in field.foreignTables:
+            self.addDependency(d)
+        self.schemaReady = False
         return field
         
     def field(self, name):
+        &quot;&quot;&quot;Returns the named field&quot;&quot;&quot;
         return self._fields[name]
     
     def fields(self):
+        &quot;&quot;&quot;Returns all registered fields&quot;&quot;&quot;
         return self._fields.values()
         
     def __getitem__(self, name):
+        &quot;&quot;&quot;Returns the named field&quot;&quot;&quot;
         return self.field(name)
     
     def select(self, *args, **kwargs):
+        &quot;&quot;&quot;See Database.select for usage.&quot;&quot;&quot;
         return self.db.select(self, *args, **kwargs)
     
+    def insert(self, *args, **kwargs):
+        &quot;&quot;&quot;See Database.insert for usage.&quot;&quot;&quot;
+        return self.db.insert(self, *args, **kwargs)
+    
+    def update(self, *args, **kwargs):
+        return self.db.update(self, *args, **kwargs)
+    
+    def __str__(self):
+        return self.name
+    
     def __repr__(self):
-        sql = &quot;create table %s&quot;%self.name
-        sql += &quot; ( &quot;+reduce((lambda x,y:repr(x)+&quot;, &quot;+repr(y.schema())), self._fields.values())+&quot; )&quot;
+        &quot;&quot;&quot;Creates the CREATE TABLE ... sql&quot;&quot;&quot;
+        sql = &quot;create table if not exists %s&quot;%self.name
+        fields = []
+        for f in self._fields.values():
+            fields.append(repr(f))
+        sql += &quot; ( &quot;+(&quot;, &quot;.join(fields))+&quot; )&quot;
         return sql
 
 class Database:
+    &quot;&quot;&quot;Represents a database connection&quot;&quot;&quot;
     connection = None
+    alltables = {}
     def __init__(self):
         self.log = logging.getLogger(&quot;Pointything.Database&quot;)
         self.log.debug(&quot;Connecting to database&quot;)
         if Database.connection == None:
             Database.connection = sqlite3.connect(&quot;/home/trever/Projects/Pointything5/database&quot;)
             Database.connection.row_factory = sqlite3.Row
+    
+    def tables(self):
+        return Database.alltables
+    
+    def table(self, name):
+        return Database.alltables[name]
         
+    def addTable(self, table):
+        Database.alltables[table.name] = table
     
     def createTable(self, table):
-        c = self.db().cursor()
+        &quot;&quot;&quot;Executes the table's CREATE sql&quot;&quot;&quot;
+        self.log.debug(&quot;Creating table: %s&quot;%(repr(table)))
         try:
-            c.execute(table.schema())
-            self.db().commit()
-            self.log.info(&quot;Created table %s&quot;%(table.name))
-        except sqlite3.OperationlError:
-            self.db().rollback()
-            return false
-        return true
-    
-    def db(self):
+            self.query(repr(table))
+            #self.log.info(&quot;Created table %s&quot;%(table.name))
+        except sqlite3.OperationalError:
+            self.__db().rollback()
+            return False
+        return True
+    
+    def __db(self):
         return Database.connection
     
     def query(self, q, *args):
-        self.log.debug(&quot;Running query %s (%s)&quot;, q, args)
-        c = self.db().cursor()
-        c.execute(q, args)
-        self.db().commit()
+        &quot;&quot;&quot;Runs a raw query on the database connection. If you have to use this, its a bug.&quot;&quot;&quot;
+        self.log.debug(&quot;Running query %s %% %s&quot;, q, args)
+        c = self.__db().cursor()
+        try:
+            c.execute(q, args)
+        except Exception, msg:
+            self.log.error(&quot;Exception during SQL: %s&quot;, msg)
+            raise msg
         return c
     
     def select(self, table, fields, matches):
-        sql = &quot;select %s from %s where %s&quot;%(reduce(lambda x,y:str(x)+&quot;, &quot;+str(y), fields), table.name, reduce(lambda x,y:str(x)+&quot; &quot;+str(y), matches))
-        c = self.db().cursor()
-        c.execute(sql)
-        return c
\ No newline at end of file
+        &quot;&quot;&quot;Runs a SELECT &lt;fields&gt; FROM &lt;table&gt; WHERE &lt;matches&gt;&quot;&quot;&quot;
+        table.create()
+        compares = []
+        values = []
+        for f in matches:
+            compares.append(repr(f))
+            values.append(str(f.field.cast(f.value)))
+        #TODO: ANDs and ORs
+        sql = &quot;select %s from %s where %s&quot;%(reduce(lambda x,y:str(x)+&quot;, &quot;+str(y), fields), table.name, &quot; AND &quot;.join(compares))
+        return self.query(sql, *values)
+    
+    def insert(self, table, **fields):
+        &quot;&quot;&quot;Runs INSERT INTO &lt;table&gt; &lt;fields&gt; VALUES &lt;values&gt;
+        Pass in a set of keyword arguments of field=value.&quot;&quot;&quot;
+        table.create()
+        parsedValues = []
+        fieldNames = []
+        for fieldName,value in fields.iteritems():
+            field = table.field(fieldName)
+            parsedValues.append(field.cast(value))
+            fieldNames.append(fieldName)
+        sql = &quot;insert into %s (%s) values (%s)&quot;%(table.name, reduce(lambda x,y:str(x)+&quot;, &quot;+str(y), fieldNames), ','.join(['?']*len(parsedValues)))
+        return self.query(sql, *parsedValues)
+    
+    def update(self, table, conditions, **values):
+        &quot;&quot;&quot;Runs UPDATE &lt;table&gt; &lt;values&gt; WHERE &lt;conditions&gt;
+        Pass in a set of keyword arguments of field=newValue
+        &quot;&quot;&quot;
+        table.create()
+        parsedValues = []
+        fieldNames = []
+        for fieldName,value in values.iteritems():
+            field = table.field(fieldName)
+            parsedValues.append(field.cast(value))
+            fieldNames.append(fieldName+&quot;=?&quot;)
+        
+        compares = []
+        values = []
+        for f in conditions:
+            compares.append(repr(f))
+            values.append(str(f.field.cast(f.value)))
+        
+        sql = &quot;update %s set %s where %s&quot;%(table.name, &quot;, &quot;.join(fieldNames) , &quot; AND &quot;.join(compares))
+        queryValues = parsedValues+values
+        #sql = &quot;update %s set (%s) where (%s)&quot;%(table.name, reduce(lambda x,y:str(x)+&quot;, &quot;+str(y), fieldNames), ','.join(['?']*len(parsedValues)))
+        return self.query(sql, *queryValues)
\ No newline at end of file</diff>
      <filename>Database/__init__.py</filename>
    </modified>
    <modified>
      <diff>@@ -1,14 +1,21 @@
 # -*- coding: utf-8 -*-
+r&quot;&quot;&quot;Interface for the user subsystem authorization and management.
+
+Normal extensions shouldn't be concerned with this module.
+&quot;&quot;&quot;
 import Database
 
 class UserTable(Database.Table):
+    &quot;&quot;&quot;Contains users&quot;&quot;&quot;
     def __init__(self):
         Database.Table.__init__(self, &quot;users&quot;)
         self.addField(Database.Fields.Integer(&quot;user_id&quot;).addConstraint(Database.Constraints.PrimaryKey()).addConstraint(Database.Constraints.AutoIncrement()))
         self.addField(Database.Fields.Text(&quot;login&quot;))
         self.addField(Database.Fields.Text(&quot;password&quot;))
+        self.insert(login=&quot;Trever&quot;, password=&quot;Hi&quot;)
 
 class User:
+    &quot;&quot;&quot;A representation of a Pointything user. Users that exist in the database are considered &quot;valid&quot;.&quot;&quot;&quot;
     def __init__(self, id):
         tbl = UserTable()
         u = tbl.select(tbl.fields(), (Database.Matches.Equals(tbl[&quot;user_id&quot;], id),))
@@ -26,10 +33,11 @@ class User:
     
     @staticmethod
     def login(login, password):
+        &quot;&quot;&quot;Returns a valid user if the credentials are correct, and an invalid one otherwise.&quot;&quot;&quot;
         tbl = UserTable()
         u = tbl.select((tbl[&quot;user_id&quot;],), (Database.Matches.Equals(tbl[&quot;login&quot;], login), Database.Matches.Equals(tbl[&quot;password&quot;], password)))
         u = u.fetchone()
         if u == None:
             return User(0)
         else:
-            return User(u['user_id'])
\ No newline at end of file
+            return User(u['user_id'])</diff>
      <filename>Pointything/Auth.py</filename>
    </modified>
    <modified>
      <diff>@@ -1,11 +1,56 @@
 # -*- coding: utf-8 -*-
+r&quot;&quot;&quot;Classes for dealing with extensions, Pointything actions, and the passing of data between actions&quot;&quot;&quot;
 from functools import wraps
 import logging
+import sys
+import imp
+
+__all__ = [&quot;Extension&quot;, &quot;Output&quot;, &quot;Action&quot;, &quot;InputHandler&quot;]
 
 class Extension:
-    extension_name = &quot;&quot;
+    &quot;&quot;&quot;The heart of Pointything's extensibility.
+    
+    Extension classes are meant to be loaded, unloaded, and reloaded on the fly with minimal hassle.
+    Normaly, __init__() and __del__() would be used to handle the loading and unloading. However,
+    /reloading/ an extension adds another level of complexity. It isn't enough to simply create
+    a new instance of an Extension and delete the old, because the state of an extension might want
+    to be preserved between reloads. The following set of methods were designed to handle this:
+    
+    * init - Called when the extension is first seen
+    * unloaded - Called when the extension is last seen
+    * reloading - Called on the new instance when it is about to replace the old one
+    * disposed - Called on the old instance after it has been supplanted by the new instance.
+    
+    For every Extension, __init__ and __del__ get handled apropriately on a per-instance basis.
+    For simple extensions that don't need to keep a state over Pointything's lifetime, that is
+    all they need. However, what if you are writing an extension that needs to keep a set of
+    memos in memory? Using only __init__, your internal memo variables would be wiped at every
+    extension reload. This is where these magic functions come in. To transfer the memo list
+    between module reloads, this memo module would need to implement the reloading method:
+    
+    def reloading(self, old):
+        self.memoList = old.memoList
+    
+    And your old memo list is now stored in the new extension instance.
+    
+    The init and unloaded methods work in a similar fashion. When an extension is first seen in Python,
+    it gets instantiated (which calls __init__), then init is called. Between then and the end of
+    the extension's life, reloads may happen (which use reloading/disposed), and finally unloaded is called.
+    
+    Extensions always get unloaded when the main loop exits, however they may also be unloaded on-demand.
+    
+    Each Extension contains a set of actions (decorated with Action) that may be called by
+    Pointything's users. Simply marking it with @Action tags it for inclusion in the default
+    implementation of userMethods.
+    
+    Actions get added to pointything with two names. The first is the actionName, the second is of the form
+    repr(extension)+&quot;.&quot;+actionName.
+    
+    Other extensions may register actions with the same short names, but they may not claim another
+    action's full name.
+    &quot;&quot;&quot;
     def __init__(self, bot):
-        self.log = logging.getLogger(&quot;Pointything.extensions.%s&quot;%(self.__class__.extension_name))
+        self.log = logging.getLogger(&quot;Pointything.extensions.%s&quot;%repr(self))
     
     def __getattribute__(self, name):
         for m in self.userMethods():
@@ -15,6 +60,7 @@ class Extension:
             return getattr(self, name)
     
     def userMethods(self):
+        &quot;&quot;&quot;Must return a list of available methods&quot;&quot;&quot;
         ret = []
         for m in dir(self):
             m = getattr(self, m)
@@ -22,10 +68,35 @@ class Extension:
                 ret.append(m)
         return ret
     
+    def init(self):
+        &quot;&quot;&quot;Called when the extension is first created&quot;&quot;&quot;
+    
     def unloaded(self):
+        &quot;&quot;&quot;Called when the extension is unloaded&quot;&quot;&quot;
+        pass
+        
+    def reloading(self, oldInstance):
+        &quot;&quot;&quot;Called when the extension has been replaced with a new instance&quot;&quot;&quot;
+        pass
+        
+    def disposed(self, newInstance):
+        &quot;&quot;&quot;Called when the old extension has been replaced with the new one.&quot;&quot;&quot;
         pass
+        
+    def readBanter(self, input, user):
+        &quot;&quot;&quot;Called to handle text recieved that isn't a command&quot;&quot;&quot;
+        pass
+    
+    def __str__(self):
+        &quot;&quot;&quot;Returns the user-friendly name of the extension&quot;&quot;&quot;
+        return repr(self)
+        
+    def __repr__(self):
+        &quot;&quot;&quot;Returns the internal representation of the extension. Overriding this is not recommended.&quot;&quot;&quot;
+        return self.__class__.__name__
 
 def Action(name=None):
+    &quot;&quot;&quot;Methods decorated with this become tagged for inclusion in the default implementation of Extension.userMethods() and have their return value automatically wrapped inside an Output object.&quot;&quot;&quot;
     if type(name)==str:
         def wrapper(func):
             @wraps(func)
@@ -42,13 +113,14 @@ def Action(name=None):
         return outputWrap
 
 class Output(list):
+    &quot;&quot;&quot;Standardized object for passing data between actions&quot;&quot;&quot;
     def __init__(self, input=None, user=None, bot=None, delimiter=None):
         if delimiter == None:
             self.delimiter = &quot; &quot;
         self.bot = bot
         self.user = user
         list.__init__(self)
-        if type(input)==str or type(input) == unicode:
+        if type(input)==str or type(input) == unicode or type(input) == int:
             self.append(input)
         elif input != None:
             if isinstance(input,Output):
@@ -68,11 +140,79 @@ class Output(list):
         return &quot;&quot;
     
     def do(self, command, out, user):
+        &quot;&quot;&quot;For chaining commands&quot;&quot;&quot;
         pass
 
 class InputHandler(Extension):
+    &quot;&quot;&quot;Describes an extension that can handle input streams&quot;&quot;&quot;
     def parse(self, bot, stream):
+        &quot;&quot;&quot;Called when select() on a stream (from InputHandler.streams) notifies that it has data ready to be read.&quot;&quot;&quot;
         pass
 
     def streams(self):
-        pass
\ No newline at end of file
+        &quot;&quot;&quot;Must return a list of file-like objects that eventually make their way to select() in the main event loop&quot;&quot;&quot;
+        pass
+        
+class ExtensionControl(Extension):
+    '''Extension to control the extensions. Thats deep, man.'''
+    def __str__(self):
+        return &quot;Extension Management&quot;
+    def __init__(self, bot):
+        Extension.__init__(self, bot)
+        extPath = &quot;/home/trever/Projects/Pointything5/Pointything/modules&quot;
+        self.log.debug(&quot;Looking for extensions in %s&quot;,extPath)
+        sys.path.append(extPath)
+    
+    @Action
+    def loadModule(self, input, modName):
+        &quot;&quot;&quot;Load a module&quot;&quot;&quot;
+        self.log.info(&quot;Loading module %s&quot;,modName)
+        self.log.debug(&quot;Looking for module %s&quot;, modName)
+        (file, path, desc) = imp.find_module(modName)
+        self.log.debug(&quot;Found in %s&quot;, path)
+        mod = imp.load_module(modName, file, path, desc)
+        foundExtensions = []
+        for item in dir(mod):
+            ext = getattr(mod, item)
+            if type(ext) == type(Extension) and issubclass(ext, Extension) and ext != Extension and ext != InputHandler:
+                self.log.debug(&quot;Found extension %s&quot;, ext)
+                foundExtensions.append(ext)
+                input.bot.extendWith(ext)
+        self.log.info(&quot;Loaded %s with extensions: %s&quot;, modName, map(lambda x:x.__name__,foundExtensions))
+        return &quot;Loading complete. Found extensions: %s&quot;%(foundExtensions)
+    
+    @Action
+    def details(self, input, extName):
+        &quot;&quot;&quot;Give details about a module&quot;&quot;&quot;
+        ext = input.bot.grabExtension(extName)
+        details = {}
+        details[&quot;Name&quot;] = str(ext)
+        details[&quot;Desc&quot;] = ext.__doc__
+        details[&quot;Class&quot;] = repr(ext)
+        return details
+    
+    @Action
+    def unload(self, input, extName):
+        &quot;&quot;&quot;Unload a module by name&quot;&quot;&quot;
+        self.log.info(&quot;Unloading extension %s&quot;, extName)
+        input.bot.unloadExtension(input.bot.grabExtension(extName))
+        return &quot;Unloaded extension&quot;
+        
+    @Action
+    def functions(self, input, module=None):
+        &quot;&quot;&quot;Returns a list of all functions declared by all loaded extensions&quot;&quot;&quot;
+        ret = []
+        if module!=None:
+            for i in input.bot.extensions[module].userMethods():
+                ret.append(i.action_name)
+        else:
+            ret = input.bot.commands
+        return ret
+    
+    @Action
+    def extensions(self, bot):
+        &quot;&quot;&quot;Lists all loaded extensions&quot;&quot;&quot;
+        ret = []
+        for m in input.bot.extensions:
+            ret.append(m)
+        return ret
\ No newline at end of file</diff>
      <filename>Pointything/Extensions.py</filename>
    </modified>
    <modified>
      <diff>@@ -1,69 +1,12 @@
 # -*- coding: utf-8 -*-
-import imp
+r&quot;&quot;&quot;Pointything, The Famous Whatshisface&quot;&quot;&quot;
 from Extensions import *
-import sys
+from Extensions import ExtensionControl
 import select
 import logging
 import __builtin__
 import traceback
-
-class ExtensionControl(Extension):
-    '''Extension to control the extensions. Thats deep, man.'''
-    extension_name = &quot;extensionControl&quot;
-    def __init__(self, bot):
-        Extension.__init__(self, bot)
-        extPath = &quot;/home/trever/Projects/Pointything5/Pointything/modules&quot;
-        self.log.debug(&quot;Looking for extensions in %s&quot;,extPath)
-        sys.path.append(extPath)
-    
-    @Action
-    def loadModule(self, input, modName):
-        self.log.info(&quot;Loading module %s&quot;,modName)
-        self.log.debug(&quot;Looking for module %s&quot;, modName)
-        (file, path, desc) = imp.find_module(modName)
-        self.log.debug(&quot;Found in %s&quot;, path)
-        mod = imp.load_module(modName, file, path, desc)
-        foundExtensions = []
-        for item in dir(mod):
-            ext = getattr(mod, item)
-            if type(ext) == type(Extension) and issubclass(ext, Extension) and ext != Extension and ext != InputHandler:
-                self.log.debug(&quot;Found extension %s&quot;, ext.extension_name)
-                foundExtensions.append(ext.extension_name)
-                input.bot.extendWith(ext)
-        self.log.info(&quot;Loaded %s with extensions: %s&quot;, modName, foundExtensions)
-        return &quot;Loading complete. Found extensions: %s&quot;%(foundExtensions)
-    
-    @Action
-    def details(self, input, extName):
-        ext = input.bot.grabExtension(extName)
-        details = {}
-        details[&quot;Name&quot;] = ext.extension_name
-        details[&quot;Desc&quot;] = ext.__doc__
-        details[&quot;Class&quot;] = str(ext)
-        return details
-    
-    @Action
-    def unload(self, input, extName):
-        self.log.info(&quot;Unloading extension %s&quot;, extName)
-        input.bot.unloadExtension(input.bot.grabExtension(extName))
-        return &quot;Unloaded extension&quot;
-        
-    @Action
-    def functions(self, input, module=None):
-        ret = []
-        if module!=None:
-            for i in input.bot.extensions[module].userMethods():
-                ret.append(i.action_name)
-        else:
-            ret = input.bot.commands
-        return ret
-    
-    @Action
-    def extensions(self, bot):
-        ret = []
-        for m in input.bot.extensions:
-            ret.append(m)
-        return ret
+import sys
 
 class Pointything:
     def __init__(self):
@@ -74,6 +17,7 @@ class Pointything:
         self.extendWith(ExtensionControl)
 
     def do(self, command, input, user=None):
+        &quot;&quot;&quot;'Does' a command. The heart of Pointything.&quot;&quot;&quot;
         input = Output(input, user, self)
         if command in self.commands:
             command = self.commands[command]
@@ -89,7 +33,12 @@ class Pointything:
         out.do = doWrapper
         return out
 
+    def readBanter(self, input, user=None):
+        for ext in self.extensions.values():
+            ext.readBanter(input, user)
+    
     def run(self):
+        &quot;&quot;&quot;Begins the main loop&quot;&quot;&quot;
         self.log.info(&quot;Entering main loop.&quot;)
         while True:
             streamToListener = {}
@@ -116,38 +65,58 @@ class Pointything:
         self.cleanup()
     
     def cleanup(self):
+        &quot;&quot;&quot;Performs some end-of-life maintenence, including unloading modules and closing inputs.&quot;&quot;&quot;
         self.log.debug(&quot;Cleaning up modules...&quot;)
         extList = self.extensions.copy()
         for m in extList:
             self.unloadExtension(self.extensions[m])
 
     def extendWith(self, ext):
+        &quot;&quot;&quot;Loads an instance of ext
+        
+        ext is _NOT_ an instance of an Extension class. It is the class itself.
+        Extensions must be initialized properly by Pointything to work right.
+        
+        &quot;&quot;&quot;
         log = logging.getLogger(&quot;Pointything.extensions&quot;)
-        log.info(&quot;Loading extension %s&quot;, ext.extension_name)
+        log.debug(&quot;Creating new instance of extension %s&quot;, ext)
         extInstance = ext(self)
-        #self.unloadExtension(ext)
-        self.extensions[ext.extension_name]=extInstance
+        try:
+            old = self.grabExtension(repr(extInstance))
+        except:
+            old = None
+        if old == None:
+            log.info(&quot;Loading extension %s (%s)&quot;, repr(extInstance), extInstance)
+            extInstance.init()
+        else:
+            log.info(&quot;Reloading extension %s (%s)&quot;, repr(extInstance), extInstance)
+            extInstance.reloading(old)
+            old.disposed(extInstance)
+            del old
+        self.extensions[repr(extInstance)]=extInstance
         for cmd in extInstance.userMethods():
-                self.commands[ext.extension_name+&quot;.&quot;+cmd.action_name] = cmd
+                self.commands[repr(extInstance)+&quot;.&quot;+cmd.action_name] = cmd
                 self.commands[cmd.action_name] = cmd
         if isinstance(extInstance, InputHandler):
-            log.info(&quot;Attaching %s as input&quot;, ext.extension_name)
+            log.info(&quot;Attaching %s as input&quot;, extInstance)
             self.inputs.append(extInstance)
     
     def grabExtension(self, extName):
+        &quot;&quot;&quot;Returns the extension with the given name&quot;&quot;&quot;
         return self.extensions[extName]
     
     def unloadExtension(self, ext):
+        &quot;&quot;&quot;Unloads the extension&quot;&quot;&quot;
         log = logging.getLogger(&quot;Pointything.extensions&quot;)
         if ext in self.extensions.values():
-            log.info(&quot;Unloading extension %s&quot;, ext.extension_name)
+            log.info(&quot;Unloading extension %s (%s)&quot;, repr(ext), ext)
             for cmd in ext.userMethods():
-                log.debug(&quot;Unregistering commands %s, %s&quot;, cmd.action_name, ext.extension_name+&quot;.&quot;+cmd.action_name)
+                log.debug(&quot;Unregistering commands %s, %s&quot;, cmd.action_name, repr(ext)+&quot;.&quot;+cmd.action_name)
                 if self.commands[cmd.action_name] == cmd:
                     del self.commands[cmd.action_name]
-                del self.commands[ext.extension_name+&quot;.&quot;+cmd.action_name]
-            del self.extensions[ext.extension_name]
+                del self.commands[repr(ext)+&quot;.&quot;+cmd.action_name]
+            del self.extensions[repr(ext)]
         if ext in self.inputs:
-            log.info(&quot;Detatching input %s&quot;, ext.extension_name)
+            log.info(&quot;Detatching input %s&quot;, ext)
             self.inputs.remove(ext)
         ext.unloaded()
\ No newline at end of file</diff>
      <filename>Pointything/__init__.py</filename>
    </modified>
    <modified>
      <diff>@@ -3,7 +3,6 @@ from Pointything.Extensions import *
 import random
 
 class EightBall(Extension):
-    extension_name=&quot;8ball&quot;
     
     ANSWERS=[
         &quot;As I see it, yes&quot;,</diff>
      <filename>Pointything/modules/Games.py</filename>
    </modified>
    <modified>
      <diff>@@ -1,9 +1,8 @@
 # -*- coding: utf-8 -*-
 from Pointything.Extensions import *
 
-class HelpExtension(Extension):
-    extension_name=&quot;help&quot;
+class Help(Extension):
     
     @Action
     def help(self, bot, *args):
-        return &quot;Ok&quot;
\ No newline at end of file
+        return &quot;Ok&quot;</diff>
      <filename>Pointything/modules/Help.py</filename>
    </modified>
    <modified>
      <diff>@@ -2,7 +2,7 @@
 import irclib
 irclib.DEBUG = 1
 class IRCInput(InputHandler):
-    extension_name=&quot;IRC&quot;
+
     def __init__(self, bot):
         InputHandler.__init__(self, bot)
         self.bot = bot
@@ -37,6 +37,7 @@ class IRCInput(InputHandler):
         elif first.startswith(&quot;~&quot;):
             args[0] = args[0][1:]
         else:
+            self.bot.readBanter(event.arguments()[0])
             return
         try:
             server.privmsg(event.target(),str(self.bot.do(*args)))
@@ -51,4 +52,4 @@ class IRCInput(InputHandler):
             server.privmsg(irclib.nm_to_n(event.source()),str(msg))
     
     def streams(self):
-        return self.ircSockets
\ No newline at end of file
+        return self.ircSockets</diff>
      <filename>Pointything/modules/IRC.py</filename>
    </modified>
    <modified>
      <diff>@@ -2,7 +2,6 @@
 from Pointything.Extensions import *
 
 class StringTransform(Extension):
-    extension_name = &quot;strings&quot;
     
     @Action(&quot;concat&quot;)
     def concat(self, bot, *args, **kwargs):
@@ -20,4 +19,4 @@ class StringTransform(Extension):
         for i in args:
             input.append(i)
         input.reverse()
-        return input
\ No newline at end of file
+        return input</diff>
      <filename>Pointything/modules/StringTransform.py</filename>
    </modified>
    <modified>
      <diff>@@ -6,6 +6,21 @@ import threading
 import traceback
 import sys
 import atexit
+import logging
+
+class TelnetLogHandler(logging.Handler):
+    def __init__(self, handler):
+        logging.Handler.__init__(self)
+        self.handler = handler
+        
+    def emit(self, record):
+        for c in self.handler.clients:
+            try:
+                c.send(self.format(record)+&quot;\n&quot;)
+            except socket.error:
+                pass
+            except:
+                self.handleError(record)
 
 class TelnetListener(threading.Thread):
     def __init__(self, handler):
@@ -13,9 +28,16 @@ class TelnetListener(threading.Thread):
         self.running = False
         self.handler = handler
         self.daemon = True
-        self.handler.log.info(&quot;Listening on *:16161&quot;)
         self.server = socket.socket()
-        self.server.bind((&quot;0.0.0.0&quot;, 16161))
+        bound = False
+        port = 16161
+        while not bound:
+            try:
+              self.server.bind((&quot;0.0.0.0&quot;, port))
+              bound = True
+            except socket.error:
+              port+=1
+        self.handler.log.info(&quot;Listening on *:%s&quot;, port)
         self.server.listen(1)
     
     def run(self):
@@ -36,19 +58,34 @@ class TelnetListener(threading.Thread):
         self.running = False
 
 class TelnetInput(InputHandler):
-    extension_name=&quot;Telnet&quot;
     def __init__(self, bot):
         InputHandler.__init__(self, bot)
         self.clients = []
         self.buffers = {}
+        self.listenThread = None
+        self.logHandler = None
+    
+    def __str__(self):
+        return &quot;Telnet Interface&quot;
+    
+    def init(self):
         self.listenThread = TelnetListener(self)
         self.listenThread.start()
-        
+        self.logHandler = TelnetLogHandler(self)
+        logging.root.addHandler(self.logHandler)
+    
     def unloaded(self):
         self.stopListener();
+        listenThread = None
         for s in self.clients:
             s.close()
     
+    def reloading(self, old):
+        self.listenThread = old.listenThread
+        self.clients = old.clients
+        self.buffers = old.buffers
+        self.logHandler = old.logHandler
+    
     def stopListener(self):
         self.log.info(&quot;Shutting down listener thread...&quot;)
         self.listenThread.kill()
@@ -69,8 +106,11 @@ class TelnetInput(InputHandler):
         command = args[0]
         input = Output(args[1:], User(1))
         try:
+            #bot.readBanter(line)
             ret = bot.do(command, input)
             stream.send(str(ret)+&quot;\n&quot;)
+        except NotImplementedError:
+            stream.send(&quot;Unknown command: %s\n&quot;%command)
         except:
             exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
             out = traceback.format_exception(exceptionType, exceptionValue, exceptionTraceback)
@@ -78,4 +118,4 @@ class TelnetInput(InputHandler):
                 stream.send(line)
     
     def streams(self):
-        return self.clients
\ No newline at end of file
+        return self.clients</diff>
      <filename>Pointything/modules/Telnet.py</filename>
    </modified>
    <modified>
      <diff>@@ -6,8 +6,10 @@ import logging
 
 if __name__ == &quot;__main__&quot;:
     logging.basicConfig(level=logging.DEBUG)
+    logging.getLogger(&quot;Pointything.Database&quot;).setLevel(logging.INFO)
     pthang = Pointything.Pointything()
     pthang.do(&quot;loadModule&quot;, &quot;Telnet&quot;)
+    pthang.do(&quot;loadModule&quot;, &quot;Markov&quot;)
     try:
         pthang.run()
     except KeyboardInterrupt:</diff>
      <filename>main.py</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>1d331a8b2bf5740f9ef47b0ef95cb86fc70ff72e</id>
    </parent>
  </parents>
  <author>
    <name>Trever Fischer</name>
    <email>wm161@wm161.net</email>
  </author>
  <url>http://github.com/workman161/pointything/commit/6d5dc331effad7c300534e465e20b2d028e4a6d4</url>
  <id>6d5dc331effad7c300534e465e20b2d028e4a6d4</id>
  <committed-date>2009-01-14T23:37:40-08:00</committed-date>
  <authored-date>2009-01-14T23:37:40-08:00</authored-date>
  <message>* Lots of documentation
* Changed module API (again) to use repr() and str() for machine and user-friendly names
* Finished more chunks of the database API
* New Modules: Logging, db, Test, Markov
* New Extensions: LogControl, Database, Testing, Markov
* Forwarded logging output to all telnet sessions</message>
  <tree>c870ac9096cab9365c94586dac5a98a6fac01a72</tree>
  <committer>
    <name>Trever Fischer</name>
    <email>wm161@wm161.net</email>
  </committer>
</commit>
