Browse Source

CommandManager as attr of Bot, plus cleanup

tags/v0.1^2
Ben Kurtovic 12 years ago
parent
commit
abe58a07f6
6 changed files with 74 additions and 63 deletions
  1. +13
    -9
      earwigbot/bot.py
  2. +34
    -41
      earwigbot/commands/__init__.py
  3. +4
    -4
      earwigbot/irc/frontend.py
  4. +5
    -3
      earwigbot/irc/watcher.py
  5. +14
    -4
      earwigbot/util.py
  6. +4
    -2
      setup.py

+ 13
- 9
earwigbot/bot.py View File

@@ -23,10 +23,13 @@
import threading import threading
from time import sleep, time from time import sleep, time


from earwigbot.commands import CommandManager
from earwigbot.config import BotConfig from earwigbot.config import BotConfig
from earwigbot.irc import Frontend, Watcher from earwigbot.irc import Frontend, Watcher
from earwigbot.tasks import task_manager from earwigbot.tasks import task_manager


__all__ = ["Bot"]

class Bot(object): class Bot(object):
""" """
The Bot class is the core of EarwigBot, essentially responsible for The Bot class is the core of EarwigBot, essentially responsible for
@@ -46,16 +49,14 @@ class Bot(object):
def __init__(self, root_dir): def __init__(self, root_dir):
self.config = BotConfig(root_dir) self.config = BotConfig(root_dir)
self.logger = logging.getLogger("earwigbot") self.logger = logging.getLogger("earwigbot")
self.commands = CommandManager(self)
self.tasks = None
self.frontend = None self.frontend = None
self.watcher = None self.watcher = None


self._keep_scheduling = True self._keep_scheduling = True
self._lock = threading.Lock() self._lock = threading.Lock()


def _start_thread(self, name, target):
thread = threading.Thread(name=name, target=target)
thread.start()

def _wiki_scheduler(self): def _wiki_scheduler(self):
while self._keep_scheduling: while self._keep_scheduling:
time_start = time() time_start = time()
@@ -68,17 +69,18 @@ class Bot(object):
def _start_components(self): def _start_components(self):
if self.config.components.get("irc_frontend"): if self.config.components.get("irc_frontend"):
self.logger.info("Starting IRC frontend") self.logger.info("Starting IRC frontend")
self.frontend = Frontend(self.config)
self._start_thread(name, self.frontend.loop)
self.frontend = Frontend(self)
self.commands.load()
threading.Thread(name=name, target=self.frontend.loop).start()


if self.config.components.get("irc_watcher"): if self.config.components.get("irc_watcher"):
self.logger.info("Starting IRC watcher") self.logger.info("Starting IRC watcher")
self.watcher = Watcher(self.config, self.frontend)
self._start_thread(name, self.watcher.loop)
self.watcher = Watcher(self)
threading.Thread(name=name, target=self.watcher.loop).start()


if self.config.components.get("wiki_scheduler"): if self.config.components.get("wiki_scheduler"):
self.logger.info("Starting wiki scheduler") self.logger.info("Starting wiki scheduler")
self._start_thread(name, self._wiki_scheduler)
threading.Thread(name=name, target=self._wiki_scheduler).start()


def _loop(self): def _loop(self):
while 1: while 1:
@@ -104,6 +106,7 @@ class Bot(object):
with self._lock: with self._lock:
self.config.load() self.config.load()
#if self.config.components.get("irc_frontend"): #if self.config.components.get("irc_frontend"):
# self.commands.load()


def stop(self): def stop(self):
if self.frontend: if self.frontend:
@@ -111,3 +114,4 @@ class Bot(object):
if self.watcher: if self.watcher:
self.watcher.stop() self.watcher.stop()
self._keep_scheduling = False self._keep_scheduling = False
sleep(3) # Give a few seconds to finish closing IRC connections

+ 34
- 41
earwigbot/commands/__init__.py View File

@@ -25,17 +25,16 @@ EarwigBot's IRC Command Manager


This package provides the IRC "commands" used by the bot's front-end component. This package provides the IRC "commands" used by the bot's front-end component.
This module contains the BaseCommand class (import with This module contains the BaseCommand class (import with
`from earwigbot.commands import BaseCommand`) and an internal _CommandManager
class. This can be accessed through the `command_manager` singleton.
`from earwigbot.commands import BaseCommand`) and an internal CommandManager
class. This can be accessed through `bot.commands`.
""" """


import imp
import logging import logging
import os
import sys
from os import listdir, path
from re import sub


from earwigbot.config import config

__all__ = ["BaseCommand", "command_manager"]
__all__ = ["BaseCommand", "CommandManager"]


class BaseCommand(object): class BaseCommand(object):
"""A base class for commands on IRC. """A base class for commands on IRC.
@@ -88,32 +87,33 @@ class BaseCommand(object):
pass pass




class _CommandManager(object):
def __init__(self):
class CommandManager(object):
def __init__(self, bot):
self.bot = bot
self.logger = logging.getLogger("earwigbot.tasks") self.logger = logging.getLogger("earwigbot.tasks")
self._base_dir = os.path.dirname(os.path.abspath(__file__))
self._connection = None
self._dirs = [path.dirname(__file__), bot.config.root_dir]
self._commands = {} self._commands = {}


def _load_command(self, filename):
"""Load a specific command from a module, identified by filename.
def _load_command(self, name, path):
"""Load a specific command from a module, identified by name and path.


Given a Connection object and a filename, we'll first try to import
it, and if that works, make an instance of the 'Command' class inside
(assuming it is an instance of BaseCommand), add it to self._commands,
and log the addition. Any problems along the way will either be
ignored or logged.
We'll first try to import it using imp magic, and if that works, make
an instance of the 'Command' class inside (assuming it is an instance
of BaseCommand), add it to self._commands, and log the addition. Any
problems along the way will either be ignored or logged.
""" """
# Strip .py from the filename's end and join with our package name:
name = ".".join(("commands", filename[:-3]))
f, path, desc = imp.find_module(name, [path])
try: try:
__import__(name)
except:
self.logger.exception("Couldn't load file {0}".format(filename))
module = imp.load_module(name, f, path, desc)
except Exception:
e = "Couldn't load module {0} from {1}"
self.logger.exception(e.format(name, path))
return return
finally:
f.close()


try: try:
command = sys.modules[name].Command(self._connection)
command = module.Command(self.bot.frontend)
except AttributeError: except AttributeError:
return # No command in this module return # No command in this module
if not isinstance(command, BaseCommand): if not isinstance(command, BaseCommand):
@@ -122,20 +122,16 @@ class _CommandManager(object):
self._commands[command.name] = command self._commands[command.name] = command
self.logger.debug("Added command {0}".format(command.name)) self.logger.debug("Added command {0}".format(command.name))


def load(self, connection):
"""Load all valid commands into self._commands.

`connection` is a Connection object that is given to each command's
constructor.
"""
self._connection = connection

files = os.listdir(self._base_dir)
files.sort()
for filename in files:
if filename.startswith("_") or not filename.endswith(".py"):
continue
self._load_command(filename)
def load(self):
"""Load (or reload) all valid commands into self._commands."""
dirs = [path.join(path.dirname(__file__), "commands"),
path.join(bot.config.root_dir, "commands")]
for dir in dirs:
files = listdir(dir)
files = [sub("\.pyc?$", "", f) for f in files if f[0] != "_"]
files = list(set(files)) # Remove duplicates
for filename in sorted(files):
self._load_command(filename, dir)


msg = "Found {0} commands: {1}" msg = "Found {0} commands: {1}"
commands = ", ".join(self._commands.keys()) commands = ", ".join(self._commands.keys())
@@ -158,6 +154,3 @@ class _CommandManager(object):
e = "Error executing command '{0}'" e = "Error executing command '{0}'"
self.logger.exception(e.format(data.command)) self.logger.exception(e.format(data.command))
break break


command_manager = _CommandManager()

+ 4
- 4
earwigbot/irc/frontend.py View File

@@ -23,7 +23,6 @@
import logging import logging
import re import re


from earwigbot.commands import command_manager
from earwigbot.irc import IRCConnection, Data, BrokenSocketException from earwigbot.irc import IRCConnection, Data, BrokenSocketException


__all__ = ["Frontend"] __all__ = ["Frontend"]
@@ -40,14 +39,15 @@ class Frontend(IRCConnection):
""" """
sender_regex = re.compile(":(.*?)!(.*?)@(.*?)\Z") sender_regex = re.compile(":(.*?)!(.*?)@(.*?)\Z")


def __init__(self, config):
self.config = config
def __init__(self, bot):
self.bot = bot
self.config = bot.config
self.logger = logging.getLogger("earwigbot.frontend") self.logger = logging.getLogger("earwigbot.frontend")

cf = config.irc["frontend"] cf = config.irc["frontend"]
base = super(Frontend, self) base = super(Frontend, self)
base.__init__(cf["host"], cf["port"], cf["nick"], cf["ident"], base.__init__(cf["host"], cf["port"], cf["nick"], cf["ident"],
cf["realname"], self.logger) cf["realname"], self.logger)
command_manager.load(self)
self._connect() self._connect()


def _process_message(self, line): def _process_message(self, line):


+ 5
- 3
earwigbot/irc/watcher.py View File

@@ -38,14 +38,16 @@ class Watcher(IRCConnection):
to channels on the IRC frontend. to channels on the IRC frontend.
""" """


def __init__(self, config, frontend=None):
self.config = config
def __init__(self, bot):
self.bot = bot
self.config = bot.config
self.frontend = bot.frontend
self.logger = logging.getLogger("earwigbot.watcher") self.logger = logging.getLogger("earwigbot.watcher")

cf = config.irc["watcher"] cf = config.irc["watcher"]
base = super(Watcher, self) base = super(Watcher, self)
base.__init__(cf["host"], cf["port"], cf["nick"], cf["ident"], base.__init__(cf["host"], cf["port"], cf["nick"], cf["ident"],
cf["realname"], self.logger) cf["realname"], self.logger)
self.frontend = frontend
self._prepare_process_hook() self._prepare_process_hook()
self._connect() self._connect()




+ 14
- 4
earwigbot/util.py View File

@@ -36,12 +36,22 @@ class BotUtility(object):
return __version__ return __version__


def run(self): def run(self):
print "EarwigBot v{0}\n".format(self.version())

def main(self):
root_dir = path.abspath(path.curdir()) root_dir = path.abspath(path.curdir())
bot = Bot(root_dir) bot = Bot(root_dir)
bot.run()
try:
bot.run()
finally:
bot.stop()

def main(self):
print "EarwigBot v{0}\n".format(self.version())
parser = argparse.ArgumentParser(description=BotUtility.__doc__)

parser.add_argument("-V", "--version", action="version",
version=self.version())

args = parser.parse_args()
# args.func(args)




main = BotUtility().main main = BotUtility().main


+ 4
- 2
setup.py View File

@@ -30,8 +30,10 @@ from setuptools import setup
setup( setup(
name = "earwigbot", name = "earwigbot",
entry_points = {"console_scripts": ["earwigbot = earwigbot.util:main"]}, entry_points = {"console_scripts": ["earwigbot = earwigbot.util:main"]},
install_requires = ["PyYAML>=3.10", "oursql>=0.9.3", "oauth2>=1.5.211",
"numpy>=1.6.1", "matplotlib>=1.1.0"],
install_requires = ["PyYAML>=3.10",
"oursql>=0.9.3",
"oauth2>=1.5.211",
"matplotlib>=1.1.0"],
version = "0.1.dev", version = "0.1.dev",
author = "Ben Kurtovic", author = "Ben Kurtovic",
author_email = "ben.kurtovic@verizon.net", author_email = "ben.kurtovic@verizon.net",


Loading…
Cancel
Save