| @@ -20,6 +20,7 @@ | |||||
| # 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. | ||||
| from collections import OrderedDict | |||||
| from getpass import getpass | from getpass import getpass | ||||
| from hashlib import sha256 | from hashlib import sha256 | ||||
| import logging | import logging | ||||
| @@ -44,6 +45,7 @@ except ImportError: | |||||
| from earwigbot.config.formatter import BotFormatter | from earwigbot.config.formatter import BotFormatter | ||||
| from earwigbot.config.node import ConfigNode | from earwigbot.config.node import ConfigNode | ||||
| from earwigbot.config.ordered_yaml import OrderedLoader | |||||
| from earwigbot.config.permissions import PermissionsDB | from earwigbot.config.permissions import PermissionsDB | ||||
| from earwigbot.config.script import ConfigScript | from earwigbot.config.script import ConfigScript | ||||
| from earwigbot.exceptions import NoConfigError | from earwigbot.exceptions import NoConfigError | ||||
| @@ -120,7 +122,7 @@ class BotConfig(object): | |||||
| filename = self._config_path | filename = self._config_path | ||||
| with open(filename, 'r') as fp: | with open(filename, 'r') as fp: | ||||
| try: | try: | ||||
| self._data = yaml.load(fp) | |||||
| self._data = yaml.load(fp, OrderedLoader) | |||||
| except yaml.YAMLError: | except yaml.YAMLError: | ||||
| print "Error parsing config file {0}:".format(filename) | print "Error parsing config file {0}:".format(filename) | ||||
| raise | raise | ||||
| @@ -270,12 +272,12 @@ class BotConfig(object): | |||||
| self._load() | self._load() | ||||
| data = self._data | data = self._data | ||||
| self.components._load(data.get("components", {})) | |||||
| self.wiki._load(data.get("wiki", {})) | |||||
| self.irc._load(data.get("irc", {})) | |||||
| self.commands._load(data.get("commands", {})) | |||||
| self.tasks._load(data.get("tasks", {})) | |||||
| self.metadata._load(data.get("metadata", {})) | |||||
| self.components._load(data.get("components", OrderedDict())) | |||||
| self.wiki._load(data.get("wiki", OrderedDict())) | |||||
| self.irc._load(data.get("irc", OrderedDict())) | |||||
| self.commands._load(data.get("commands", OrderedDict())) | |||||
| self.tasks._load(data.get("tasks", OrderedDict())) | |||||
| self.metadata._load(data.get("metadata", OrderedDict())) | |||||
| self._setup_logging() | self._setup_logging() | ||||
| if self.is_encrypted(): | if self.is_encrypted(): | ||||
| @@ -20,11 +20,13 @@ | |||||
| # 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. | ||||
| from collections import OrderedDict | |||||
| __all__ = ["ConfigNode"] | __all__ = ["ConfigNode"] | ||||
| class ConfigNode(object): | class ConfigNode(object): | ||||
| def __init__(self): | def __init__(self): | ||||
| self._data = {} | |||||
| self._data = OrderedDict() | |||||
| def __repr__(self): | def __repr__(self): | ||||
| return self._data | return self._data | ||||
| @@ -99,4 +101,4 @@ class ConfigNode(object): | |||||
| return self._data.itervalues() | return self._data.itervalues() | ||||
| def iteritems(self): | def iteritems(self): | ||||
| return self.__dict__.iteritems() | |||||
| return self._data.iteritems() | |||||
| @@ -0,0 +1,107 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| # | |||||
| # Copyright (C) 2009-2012 Ben Kurtovic <ben.kurtovic@verizon.net> | |||||
| # | |||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| # of this software and associated documentation files (the "Software"), to deal | |||||
| # in the Software without restriction, including without limitation the rights | |||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| # copies of the Software, and to permit persons to whom the Software is | |||||
| # furnished to do so, subject to the following conditions: | |||||
| # | |||||
| # The above copyright notice and this permission notice shall be included in | |||||
| # all copies or substantial portions of the Software. | |||||
| # | |||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
| # SOFTWARE. | |||||
| """ | |||||
| Based on: | |||||
| * https://gist.github.com/844388 | |||||
| * http://pyyaml.org/attachment/ticket/161/use_ordered_dict.py | |||||
| with modifications. | |||||
| """ | |||||
| from collections import OrderedDict | |||||
| try: | |||||
| import yaml | |||||
| except ImportError: | |||||
| yaml = None | |||||
| __all__ = ["OrderedLoader", "OrderedDumper"] | |||||
| class OrderedLoader(yaml.Loader): | |||||
| """A YAML loader that loads mappings into ordered dictionaries.""" | |||||
| def __init__(self, *args, **kwargs): | |||||
| super(OrderedLoader, self).__init__(*args, **kwargs) | |||||
| constructor = type(self).construct_yaml_map | |||||
| self.add_constructor(u"tag:yaml.org,2002:map", constructor) | |||||
| self.add_constructor(u"tag:yaml.org,2002:omap", constructor) | |||||
| def construct_yaml_map(self, node): | |||||
| data = OrderedDict() | |||||
| yield data | |||||
| value = self.construct_mapping(node) | |||||
| data.update(value) | |||||
| def construct_mapping(self, node, deep=False): | |||||
| if isinstance(node, yaml.MappingNode): | |||||
| self.flatten_mapping(node) | |||||
| else: | |||||
| raise yaml.constructor.ConstructorError(None, None, | |||||
| "expected a mapping node, but found {0}".format(node.id), | |||||
| node.start_mark) | |||||
| mapping = OrderedDict() | |||||
| for key_node, value_node in node.value: | |||||
| key = self.construct_object(key_node, deep=deep) | |||||
| try: | |||||
| hash(key) | |||||
| except TypeError, exc: | |||||
| raise yaml.constructor.ConstructorError( | |||||
| "while constructing a mapping", node.start_mark, | |||||
| "found unacceptable key ({0})".format(exc), | |||||
| key_node.start_mark) | |||||
| value = self.construct_object(value_node, deep=deep) | |||||
| mapping[key] = value | |||||
| return mapping | |||||
| class OrderedDumper(yaml.Dumper): | |||||
| """A YAML dumper that dumps mappings into ordered dictionaries.""" | |||||
| def __init__(self, *args, **kwargs): | |||||
| super(OrderedDumper, self).__init__(*args, **kwargs) | |||||
| self.add_representer(OrderedDict, type(self).represent_dict) | |||||
| def represent_mapping(self, tag, mapping, flow_style=None): | |||||
| value = [] | |||||
| node = yaml.MappingNode(tag, value, flow_style=flow_style) | |||||
| if self.alias_key is not None: | |||||
| self.represented_objects[self.alias_key] = node | |||||
| best_style = True | |||||
| if hasattr(mapping, "items"): | |||||
| mapping = list(mapping.items()) | |||||
| for item_key, item_value in mapping: | |||||
| node_key = self.represent_data(item_key) | |||||
| node_value = self.represent_data(item_value) | |||||
| if not (isinstance(node_key, yaml.ScalarNode) and not | |||||
| node_key.style): | |||||
| best_style = False | |||||
| if not (isinstance(node_value, yaml.ScalarNode) and not | |||||
| node_value.style): | |||||
| best_style = False | |||||
| value.append((node_key, node_value)) | |||||
| if flow_style is None: | |||||
| if self.default_flow_style is not None: | |||||
| node.flow_style = self.default_flow_style | |||||
| else: | |||||
| node.flow_style = best_style | |||||
| return node | |||||
| @@ -38,6 +38,7 @@ except ImportError: | |||||
| yaml = None | yaml = None | ||||
| from earwigbot import exceptions | from earwigbot import exceptions | ||||
| from earwigbot.config.ordered_yaml import OrderedDumper | |||||
| __all__ = ["ConfigScript"] | __all__ = ["ConfigScript"] | ||||
| @@ -272,9 +273,10 @@ class ConfigScript(object): | |||||
| 'unaffiliated/nickname'.""") | 'unaffiliated/nickname'.""") | ||||
| host = self._ask("Your hostname on the IRC frontend:") | host = self._ask("Your hostname on the IRC frontend:") | ||||
| if host: | if host: | ||||
| self.config._permissions.load() | |||||
| self.config._permissions.add_owner(host=host) | |||||
| self.config._permissions.add_admin(host=host) | |||||
| permdb = self.config._permissions | |||||
| permdb.load() | |||||
| permdb.add_owner(host=host) | |||||
| permdb.add_admin(host=host) | |||||
| else: | else: | ||||
| frontend = {} | frontend = {} | ||||
| @@ -360,8 +362,8 @@ class ConfigScript(object): | |||||
| self._pause() | self._pause() | ||||
| def _save(self): | def _save(self): | ||||
| with open(self.config.path, "w") as stream: | |||||
| yaml.dump(self.data, stream=stream, default_flow_style=False) | |||||
| with open(self.config.path, "w") as strm: | |||||
| yaml.dump(self.data, strm, OrderedDumper, default_flow_style=False) | |||||
| def make_new(self): | def make_new(self): | ||||
| """Make a new config file based on the user's input.""" | """Make a new config file based on the user's input.""" | ||||
| @@ -20,6 +20,7 @@ | |||||
| # 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. | ||||
| from collections import OrderedDict | |||||
| from cookielib import LWPCookieJar, LoadError | from cookielib import LWPCookieJar, LoadError | ||||
| import errno | import errno | ||||
| from os import chmod, path | from os import chmod, path | ||||
| @@ -192,7 +193,7 @@ class SitesDB(object): | |||||
| maxlag = config.wiki.get("maxlag") | maxlag = config.wiki.get("maxlag") | ||||
| wait_between_queries = config.wiki.get("waitTime", 3) | wait_between_queries = config.wiki.get("waitTime", 3) | ||||
| logger = self._logger.getChild(name) | logger = self._logger.getChild(name) | ||||
| search_config = config.wiki.get("search", {}).copy() | |||||
| search_config = config.wiki.get("search", OrderedDict()).copy() | |||||
| if user_agent: | if user_agent: | ||||
| user_agent = user_agent.replace("$1", __version__) | user_agent = user_agent.replace("$1", __version__) | ||||
| @@ -204,7 +205,7 @@ class SitesDB(object): | |||||
| search_config["exclusions_db"] = self._exclusions_db | search_config["exclusions_db"] = self._exclusions_db | ||||
| if not sql: | if not sql: | ||||
| sql = config.wiki.get("sql", {}).copy() | |||||
| sql = config.wiki.get("sql", OrderedDict()).copy() | |||||
| for key, value in sql.iteritems(): | for key, value in sql.iteritems(): | ||||
| if isinstance(value, basestring) and "$1" in value: | if isinstance(value, basestring) and "$1" in value: | ||||
| sql[key] = value.replace("$1", name) | sql[key] = value.replace("$1", name) | ||||