# -*- coding: utf-8 -*- """ EarwigBot's XML Config File Parser This handles all tasks involving reading and writing to our config file, including encrypting and decrypting passwords and making a new config file from scratch at the inital bot run. Usually you'll just want to do "from core.config import config" and access config data from within that object. """ from collections import defaultdict from os import makedirs, path from xml.dom import minidom from xml.parsers.expat import ExpatError script_dir = path.dirname(path.abspath(__file__)) root_dir = path.split(script_dir)[0] config_path = path.join(root_dir, "config.xml") _config = None # holds the parsed DOM object for our config file config = None # holds an instance of Container() with our config data class ConfigParseException(Exception): """Base exception for when we could not parse the config file.""" class TypeMismatchException(ConfigParseException): """A field does not fit to its expected type; e.g., an arbitrary string where we expected a boolean or integer.""" class MissingElementException(ConfigParseException): """An element in the config file is missing a required sub-element.""" class MissingAttributeException(ConfigParseException): """An element is missing a required attribute to be parsed correctly.""" class Container(object): """A class to hold information in a nice, accessable manner.""" def _load_config(): """Load data from our XML config file (config.xml) into a DOM object.""" global _config _config = minidom.parse(config_path) def verify_config(): """Check to see if we have a valid config file, and if not, notify the user. If there is no config file at all, offer to make one; otherwise, exit.""" if path.exists(config_path): try: _load_config() except ExpatError as error: print "Could not parse config file {0}:\n{1}".format(config_path, error) exit() else: if not _config.getElementsByTagName("config"): e = "Config file is missing a tag." raise MissingElementException(e) return are_passwords_encrypted() else: print "You haven't configured the bot yet!" choice = raw_input("Would you like to do this now? [y/n] ") if choice.lower().startswith("y"): return make_new_config() else: exit() def make_new_config(): """Make a new XML config file based on the user's input.""" makedirs(config_dir) encrypt = raw_input("Would you like to encrypt passwords stored in " + "config.xml? [y/n] ") if encrypt.lower().startswith("y"): is_encrypted = True else: is_encrypted = False return is_encrypted def are_passwords_encrypted(): """Determine if the passwords in our config file are encrypted, returning either True or False.""" element = _config.getElementsByTagName("config")[0] return attribute_to_bool(element, "encrypt-passwords", default=False) def attribute_to_bool(element, attribute, default=None): """Return True if the value of element's attribute is 'true', '1', or 'on'; return False if it is 'false', '0', or 'off' (regardless of capitalization); return default if it is empty; raise TypeMismatchException if it does match any of those.""" value = element.getAttribute(attribute).lower() if value in ["true", "1", "on"]: return True elif value in ["false", "0", "off"]: return False elif value == '': return default else: e = ("Expected a bool in attribute '{0}' of element '{1}', but " + "got '{2}'.").format(attribute, element.tagName, value) raise TypeMismatchException(e) def parse_config(key): """Parse config data from a DOM object into the 'config' global variable. The key is used to unencrypt passwords stored in the XML config file.""" _load_config() # we might be re-loading unnecessarily here, but no harm in # that! data = _config.getElementsByTagName("config")[0] cfg = Container() cfg.components = parse_components(data) cfg.wiki = parse_wiki(data, key) cfg.irc = parse_irc(data, key) cfg.schedule = parse_schedule(data) cfg.watcher = parse_watcher(data) global config config = cfg def parse_components(data): """Parse everything within the XML tag of our config file. The components object here will exist as config.components, and is a dict of our enabled components: components[name] = True if it is enabled, False if it is disabled.""" components = defaultdict(lambda: False) # all components are disabled by # default element = data.getElementsByTagName("components") if not element: e = " is missing a required tag." raise MissingElementException(e) element = element[0] # select the first tag out of our list # of tags, even though we should only have one component_tags = element.getElementsByTagName("component") for component in component_tags: name = component.getAttribute("name") if not name: e = "A tag is missing the required attribute 'name'." raise MissingAttributeException(e) is_enabled = attribute_to_bool(component, "enabled", False) components[name] = is_enabled return components def parse_wiki(data, key): pass def parse_irc(data, key): pass def parse_schedule(data): pass def parse_watcher(data): pass