Browse Source

Wrote the command-line utility, added logging levels, improved Bot organization

- Fixed loading bugs in CommandLoader and TaskLoader
tags/v0.1^2
Ben Kurtovic 12 years ago
parent
commit
03062e808b
5 changed files with 112 additions and 73 deletions
  1. +8
    -13
      earwigbot/bot.py
  2. +20
    -11
      earwigbot/commands/__init__.py
  3. +24
    -7
      earwigbot/config.py
  4. +21
    -11
      earwigbot/tasks/__init__.py
  5. +39
    -31
      earwigbot/util.py

+ 8
- 13
earwigbot/bot.py View File

@@ -53,8 +53,8 @@ class Bot(object):
wiki toolset with bot.wiki.get_site(). wiki toolset with bot.wiki.get_site().
""" """


def __init__(self, root_dir):
self.config = BotConfig(root_dir)
def __init__(self, root_dir, level=logging.INFO):
self.config = BotConfig(root_dir, level)
self.logger = logging.getLogger("earwigbot") self.logger = logging.getLogger("earwigbot")
self.commands = CommandManager(self) self.commands = CommandManager(self)
self.tasks = TaskManager(self) self.tasks = TaskManager(self)
@@ -65,6 +65,10 @@ class Bot(object):
self.component_lock = Lock() self.component_lock = Lock()
self._keep_looping = True self._keep_looping = True


self.config.load()
self.commands.load()
self.tasks.load()

def _start_irc_components(self): def _start_irc_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")
@@ -107,18 +111,10 @@ class Bot(object):
self.logger.warn("IRC watcher has stopped; restarting") self.logger.warn("IRC watcher has stopped; restarting")
self.watcher = Watcher(self) self.watcher = Watcher(self)
Thread(name=name, target=self.watcher.loop).start() Thread(name=name, target=self.watcher.loop).start()
sleep(5)
sleep(3)


def run(self): def run(self):
config = self.config
config.load()
config.decrypt(config.wiki, "password")
config.decrypt(config.wiki, "search", "credentials", "key")
config.decrypt(config.wiki, "search", "credentials", "secret")
config.decrypt(config.irc, "frontend", "nickservPassword")
config.decrypt(config.irc, "watcher", "nickservPassword")
self.commands.load()
self.tasks.load()
self.logger.info("Starting bot")
self._start_irc_components() self._start_irc_components()
self._start_wiki_scheduler() self._start_wiki_scheduler()
self._loop() self._loop()
@@ -137,4 +133,3 @@ class Bot(object):
with self.component_lock: with self.component_lock:
self._stop_irc_components() self._stop_irc_components()
self._keep_looping = False self._keep_looping = False
sleep(3) # Give a few seconds to finish closing IRC connections

+ 20
- 11
earwigbot/commands/__init__.py View File

@@ -135,22 +135,31 @@ class CommandManager(object):
return return


self._commands[command.name] = command self._commands[command.name] = command
self.logger.debug("Added command {0}".format(command.name))
self.logger.debug("Loaded command {0}".format(command.name))

def _load_directory(self, dir):
"""Load all valid commands in a given directory."""
processed = []
for name in listdir(dir):
if not name.endswith(".py") and not name.endswith(".pyc"):
continue
if name.startswith("_") or name.startswith("."):
continue
modname = sub("\.pyc?$", "", name) # Remove extension
if modname not in processed:
self._load_command(modname, dir)
processed.append(modname)


def load(self): def load(self):
"""Load (or reload) all valid commands into self._commands.""" """Load (or reload) all valid commands into self._commands."""
with self._command_access_lock: with self._command_access_lock:
self._commands.clear() self._commands.clear()
dirs = [path.join(path.dirname(__file__), "commands"),
path.join(self.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}"
builtin_dir = path.dirname(__file__)
plugins_dir = path.join(self.bot.config.root_dir, "commands")
self._load_directory(builtin_dir) # Built-in commands
self._load_directory(plugins_dir) # Custom commands, aka plugins

msg = "Loaded {0} commands: {1}"
commands = ", ".join(self._commands.keys()) commands = ", ".join(self._commands.keys())
self.logger.info(msg.format(len(self._commands), commands)) self.logger.info(msg.format(len(self._commands), commands))




+ 24
- 7
earwigbot/config.py View File

@@ -59,8 +59,9 @@ class BotConfig(object):
aren't encrypted aren't encrypted
""" """


def __init__(self, root_dir):
def __init__(self, root_dir, level):
self._root_dir = root_dir self._root_dir = root_dir
self._logging_level = level
self._config_path = path.join(self._root_dir, "config.yml") self._config_path = path.join(self._root_dir, "config.yml")
self._log_dir = path.join(self._root_dir, "logs") self._log_dir = path.join(self._root_dir, "logs")
self._decryption_key = None self._decryption_key = None
@@ -74,7 +75,14 @@ class BotConfig(object):


self._nodes = [self._components, self._wiki, self._tasks, self._irc, self._nodes = [self._components, self._wiki, self._tasks, self._irc,
self._metadata] self._metadata]
self._decryptable_nodes = []

self._decryptable_nodes = [ # Default nodes to decrypt
(self._wiki, ("password")),
(self._wiki, ("search", "credentials", "key")),
(self._wiki, ("search", "credentials", "secret")),
(self._irc, ("frontend", "nickservPassword")),
(self._irc, ("watcher", "nickservPassword")),
]


def _load(self): def _load(self):
"""Load data from our JSON config file (config.yml) into self._data.""" """Load data from our JSON config file (config.yml) into self._data."""
@@ -119,10 +127,10 @@ class BotConfig(object):
h.setFormatter(formatter) h.setFormatter(formatter)
logger.addHandler(h) logger.addHandler(h)


stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)
stream_handler.setFormatter(color_formatter)
logger.addHandler(stream_handler)
self._stream_handler = stream = logging.StreamHandler()
stream.setLevel(self._logging_level)
stream.setFormatter(color_formatter)
logger.addHandler(stream)


def _decrypt(self, node, nodes): def _decrypt(self, node, nodes):
"""Try to decrypt the contents of a config node. Use self.decrypt().""" """Try to decrypt the contents of a config node. Use self.decrypt()."""
@@ -148,6 +156,15 @@ class BotConfig(object):
return self._root_dir return self._root_dir


@property @property
def logging_level(self):
return self._logging_level

@logging_level.setter
def logging_level(self, level):
self._logging_level = level
self._stream_handler.setLevel(level)

@property
def path(self): def path(self):
return self._config_path return self._config_path


@@ -213,7 +230,7 @@ class BotConfig(object):
if choice.lower().startswith("y"): if choice.lower().startswith("y"):
self._make_new() self._make_new()
else: else:
exit(1)
exit(1) # TODO: raise an exception instead


self._load() self._load()
data = self._data data = self._data


+ 21
- 11
earwigbot/tasks/__init__.py View File

@@ -30,6 +30,7 @@ internal TaskManager class. This can be accessed through `bot.tasks`.


import imp import imp
from os import listdir, path from os import listdir, path
from re import sub
from threading import Lock, Thread from threading import Lock, Thread
from time import gmtime, strftime from time import gmtime, strftime


@@ -186,22 +187,31 @@ class TaskManager(object):
return return


self._tasks[task.name] = task self._tasks[task.name] = task
self.logger.debug("Added task {0}".format(task.name))
self.logger.debug("Loaded task {0}".format(task.name))

def _load_directory(self, dir):
"""Load all valid tasks in a given directory."""
processed = []
for name in listdir(dir):
if not name.endswith(".py") and not name.endswith(".pyc"):
continue
if name.startswith("_") or name.startswith("."):
continue
modname = sub("\.pyc?$", "", name) # Remove extension
if modname not in processed:
self._load_task(modname, dir)
processed.append(modname)


def load(self): def load(self):
"""Load (or reload) all valid tasks into self._tasks.""" """Load (or reload) all valid tasks into self._tasks."""
with self._task_access_lock: with self._task_access_lock:
self._tasks.clear() self._tasks.clear()
dirs = [path.join(path.dirname(__file__), "tasks"),
path.join(self.bot.config.root_dir, "tasks")]
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_task(filename)

msg = "Found {0} tasks: {1}"
builtin_dir = path.dirname(__file__)
plugins_dir = path.join(self.bot.config.root_dir, "tasks")
self._load_directory(builtin_dir) # Built-in tasks
self._load_directory(plugins_dir) # Custom tasks, aka plugins

msg = "Loaded {0} tasks: {1}"
tasks = ', '.join(self._tasks.keys()) tasks = ', '.join(self._tasks.keys())
self.logger.info(msg.format(len(self._tasks), tasks)) self.logger.info(msg.format(len(self._tasks), tasks))




+ 39
- 31
earwigbot/util.py View File

@@ -21,46 +21,54 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.


"""
This is EarwigBot's command-line utility, enabling you to easily start the
bot or run specific tasks.
"""

import argparse import argparse
import logging
from os import path from os import path


from earwigbot import __version__ from earwigbot import __version__
from earwigbot.bot import Bot from earwigbot.bot import Bot


__all__ = ["BotUtility", "main"]

class BotUtility(object):
"""
This is a command-line utility for EarwigBot that enables you to easily
start the bot without writing generally unnecessary three-line bootstrap
scripts. It supports starting the bot from any directory, as well as
starting individual tasks instead of the entire bot.
"""

def version(self):
return "EarwigBot v{0}".format(__version__)

def run(self, root_dir):
bot = Bot(root_dir)
print self.version()
#try:
# bot.run()
#finally:
# bot.stop()

def main(self):
parser = argparse.ArgumentParser(description=BotUtility.__doc__)

parser.add_argument("-v", "--version", action="version",
version=self.version())
parser.add_argument("root_dir", metavar="path", nargs="?", default=path.curdir)
args = parser.parse_args()
__all__ = ["main"]


root_dir = path.abspath(args.root_dir)
self.run(root_dir)
def main():
version = "EarwigBot v{0}".format(__version__)
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("path", nargs="?", metavar="PATH", default=path.curdir,
help="path to the bot's working directory, which will be created if it doesn't exist; current directory assumed if not specified")
parser.add_argument("-v", "--version", action="version", version=version)
parser.add_argument("-d", "--debug", action="store_true",
help="print all logs, including DEBUG-level messages")
parser.add_argument("-q", "--quiet", action="store_true",
help="don't print any logs except warnings and errors")
parser.add_argument("-t", "--task", metavar="NAME",
help="given the name of a task, the bot will run it instead of the main bot and then exit")


args = parser.parse_args()
if args.debug and args.quiet:
parser.print_usage()
print "earwigbot: error: cannot show debug messages and be quiet at the same time"
return
level = logging.INFO
if args.debug:
level = logging.DEBUG
elif args.quiet:
level = logging.WARNING


main = BotUtility().main
print version
print
bot = Bot(path.abspath(args.path), level=level)
try:
if args.task:
bot.tasks.start(args.task)
else:
bot.run()
finally:
bot.stop()


if __name__ == "__main__": if __name__ == "__main__":
main() main()

Loading…
Cancel
Save