From 8c6fb2e8ba7945a9e76c34200e07f4d39be43585 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 7 Aug 2011 19:10:16 -0400 Subject: [PATCH] more cleanup, improvements, fixes, whatever; restructured command files, but haven't done all of them yet --- bot/classes/base_command.py | 43 ++++++++++++++++++------------ bot/classes/base_task.py | 24 +++++++++++------ bot/classes/connection.py | 5 ++-- bot/classes/data.py | 6 +---- bot/commands/__init__.py | 26 +++++++++++------- bot/commands/help.py | 64 +++++++++++++++++++-------------------------- bot/commands/test.py | 18 +++---------- bot/tasks/__init__.py | 6 ++--- 8 files changed, 95 insertions(+), 97 deletions(-) diff --git a/bot/classes/base_command.py b/bot/classes/base_command.py index e3d4807..710e6eb 100644 --- a/bot/classes/base_command.py +++ b/bot/classes/base_command.py @@ -1,32 +1,41 @@ # -*- coding: utf-8 -*- class BaseCommand(object): - """A base class for commands on IRC.""" + """A base class for commands on IRC. + + This docstring is reported to the user when they use !help . + """ + # This is the command's name, as reported to the user when they use !help: + name = "base_command" + + # Hooks are "msg", "msg_private", "msg_public", and "join". "msg" is the + # default behavior; if you wish to override that, change the value in your + # command subclass: + hooks = ["msg"] def __init__(self, connection): self.connection = connection - def get_hooks(self): - """Hooks are: 'msg', 'msg_private', 'msg_public', and 'join'. Return - the hooks you want this command to be called on.""" - return [] + def check(self, data): + """Returns whether this command should be called in response to 'data'. - def get_help(self, command): - """Return help information for the command, used by !help. return None - for no help. If a given class handles multiple commands, the command - variable can be used to return different help for each one.""" - return None + Given a Data() instance, return True if we should respond to this + activity, or False if we should ignore it or it doesn't apply to us. - def check(self, data): - """Given a Data() object, return True if we should respond to this - activity, or False if we should ignore it/it doesn't apply to us. Most - commands return True if data.command == 'command_name', otherwise - they return False.""" + Most commands return True if data.command == self.name, otherwise they + return False. This is the default behavior of check(); you need only + override it if you wish to change that. + """ + if data.is_command and data.command == self.name: + return True return False def process(self, data): - """Handle an activity (usually a message) on IRC. At this point, thanks + """Main entry point for doing a command. + + Handle an activity (usually a message) on IRC. At this point, thanks to self.check() which is called automatically by command_handler, we know this is something we should respond to, so (usually) a - 'if data.command != "command_name": return' is unnecessary.""" + 'if data.command != "command_name": return' is unnecessary. + """ pass diff --git a/bot/classes/base_task.py b/bot/classes/base_task.py index e5abeba..e470178 100644 --- a/bot/classes/base_task.py +++ b/bot/classes/base_task.py @@ -5,15 +5,23 @@ class BaseTask(object): task_name = None def __init__(self): - """This is called once immediately after the task class is loaded by - the task manager (in wiki.task_manager.load_class_from_file()).""" + """Constructor for new tasks. + + This is called once immediately after the task class is loaded by + the task manager (in tasks._load_class_from_file()). + """ pass def run(self, **kwargs): - """This is called directly by task_manager.start_task() and is the main - way to make a task do stuff. kwargs will be any keyword arguments - passed to start_task(), which are (of course) optional. The same task - instance is preserved between runs, so you can theoretically store data - in self (e.g. start_task('mytask', action='store', data='foo')) and - then use it later (e.g. start_task('mytask', action='save')).""" + """Main entry point to run a given task. + + This is called directly by tasks.start() and is the main way to make a + task do stuff. kwargs will be any keyword arguments passed to start() + which are entirely optional. + + The same task instance is preserved between runs, so you can + theoretically store data in self (e.g. + start('mytask', action='store', data='foo')) and then use it later + (e.g. start('mytask', action='save')). + """ pass diff --git a/bot/classes/connection.py b/bot/classes/connection.py index c108bb2..8e2774a 100644 --- a/bot/classes/connection.py +++ b/bot/classes/connection.py @@ -19,7 +19,7 @@ class Connection(object): self.ident = ident self.realname = realname - # A lock to prevent us from sending two messages at once. + # A lock to prevent us from sending two messages at once: self.lock = threading.Lock() def connect(self): @@ -58,8 +58,7 @@ class Connection(object): self.send(message) def reply(self, data, msg): - """Send a private message as a reply to a user on the server. `data` is - a Data object (or anything with chan and nick attributes).""" + """Send a private message as a reply to a user on the server.""" message = "".join((chr(2), data.nick, chr(0x0f), ": ", msg)) self.say(data.chan, message) diff --git a/bot/classes/data.py b/bot/classes/data.py index e35a3d4..7445097 100644 --- a/bot/classes/data.py +++ b/bot/classes/data.py @@ -13,11 +13,7 @@ class Data(object): def __init__(self, line): self.line = line - self.chan = str() - self.nick = str() - self.ident = str() - self.host = str() - self.msg = str() + self.chan = self.nick = self.ident = self.host = self.msg = "" def parse_args(self): """Parse command args from self.msg into self.command and self.args.""" diff --git a/bot/commands/__init__.py b/bot/commands/__init__.py index fc25de5..c479490 100644 --- a/bot/commands/__init__.py +++ b/bot/commands/__init__.py @@ -1,16 +1,23 @@ # -*- coding: utf-8 -*- -# A module to manage IRC commands. +""" +EarwigBot's IRC Command Manager + +This package provides the IRC "commands" used by the bot's front-end component. +In __init__, you can find some functions used to load and run these commands. +""" import os import traceback __all__ = ["load", "get_all", "check"] -_commands = [] +# Store commands in a dict, where the key is the command's name and the value +# is an instance of the command's class: +_commands = {} -def _process_module(connection, module): - """go through all objects in a module and add valid command classes to the commands variable""" +def _load_class_from_file(connection, module): + """Add.""" global commands objects = dir(module) @@ -30,9 +37,9 @@ def _process_module(connection, module): continue def load(connection): - """load all valid command classes from irc/commmands/ into the commands variable""" - files = os.listdir(os.path.join("irc", "commands")) # get all files in irc/commands/ - files.sort() # alphabetically sort list of files + """Load all valid commands into the _commands global variable.""" + files = os.listdir(os.path.join("bot", "commands")) + files.sort() for f in files: if f.startswith("_") or not f.endswith(".py"): # ignore non-python files or files beginning with "_" @@ -50,12 +57,11 @@ def load(connection): print "Found %s command classes: %s." % (len(commands), ', '.join(pretty_cmnds)) def get_all(): - """Return our list of all commands.""" + """Return our dict of all loaded commands.""" return _commands def check(hook, data): - """Given an event on IRC, check if there's anything we can respond to by - calling each command class""" + """Given an event on IRC, check if there's anything we can respond to.""" # parse command arguments into data.command and data.args data.parse_args() diff --git a/bot/commands/help.py b/bot/commands/help.py index 54eefa2..dc98f16 100644 --- a/bot/commands/help.py +++ b/bot/commands/help.py @@ -1,54 +1,44 @@ # -*- coding: utf-8 -*- -# Generates help information. +from classes import BaseCommand, Data +import commands -from irc.classes import BaseCommand, Data -from irc import command_handler - -class Help(BaseCommand): - def get_hooks(self): - return ["msg"] - - def get_help(self, command): - return "Generates help information." - - def check(self, data): - if data.is_command and data.command == "help": - return True - return False +class Command(BaseCommand): + """Generates help information.""" + name = "help" def process(self, data): + self.cmnds = commands.get_all().keys() if not data.args: - self.do_general_help(data) + self.do_main_help(data) else: - if data.args[0] == "list": - self.do_list_help(data) - else: - self.do_command_help(data) - - def do_general_help(self, data): - self.connection.reply(data, "I am a bot! You can get help for any command with '!help ', or a list of all loaded modules with '!help list'.") + self.do_command_help(data) - def do_list_help(self, data): - commands = command_handler.get_commands() - cmnds = map(lambda c: c.__class__.__name__, commands) - pretty_cmnds = ', '.join(cmnds) - self.connection.reply(data, "%s command classes loaded: %s." % (len(cmnds), pretty_cmnds)) + def do_main_help(self, data): + """Give the user a general help message with a list of all commands.""" + msg = "I am a bot! I have {0} commands loaded: {1}. You can get help for any command with '!help '." + msg.format(len(self.cmnds), ', '.join(self.cmnds)) + self.connection.reply(data, msg) def do_command_help(self, data): + """Give the user help for a specific command.""" command = data.args[0] - commands = command_handler.get_commands() - dummy = Data() # dummy message to test which command classes pick up this command - dummy.command = command.lower() # lowercase command name + # Create a dummy message to test which commands pick up the user's + # input: + dummy = Data() + dummy.command = command.lower() dummy.is_command = True - for cmnd in commands: + for cmnd in self.cmnds: if cmnd.check(dummy): - help = cmnd.get_help(command) + doc = cmnd.__doc__ + if doc: + msg = "info for command \x0303{0}\x0301: \"{1}\"" + msg.format(command, doc) + self.connection.reply(data, msg) + return break - try: - self.connection.reply(data, "info for command \x0303%s\x0301: \"%s\"" % (command, help)) - except UnboundLocalError: - self.connection.reply(data, "sorry, no help for \x0303%s\x0301." % command) + msg = "sorry, no help for \x0303{0}\x0301.".format(command) + self.connection.reply(data, msg) diff --git a/bot/commands/test.py b/bot/commands/test.py index 630a37f..2bb59f8 100644 --- a/bot/commands/test.py +++ b/bot/commands/test.py @@ -1,22 +1,12 @@ # -*- coding: utf-8 -*- -# A very simple command to test the bot. - import random -from irc.classes import BaseCommand - -class Test(BaseCommand): - def get_hooks(self): - return ["msg"] - - def get_help(self, command): - return "Test the bot!" +from classes import BaseCommand - def check(self, data): - if data.is_command and data.command == "test": - return True - return False +class Command(BaseCommand): + """Test the bot!""" + name = "test" def process(self, data): hey = random.randint(0, 1) diff --git a/bot/tasks/__init__.py b/bot/tasks/__init__.py index 502813a..98ae925 100644 --- a/bot/tasks/__init__.py +++ b/bot/tasks/__init__.py @@ -16,9 +16,9 @@ import config __all__ = ["load", "schedule", "start"] -# store loaded tasks as a dict where the key is the task name and the value is -# an instance of the task class (wiki.tasks.task_file.Task()) -_tasks = dict() +# Store loaded tasks as a dict where the key is the task name and the value is +# an instance of the task class: +_tasks = {} def _load_class_from_file(f): """Look in a given file for the task class."""