# -*- encoding: utf-8 -*- from __future__ import unicode_literals import re import logging import datetime from email.utils import parseaddr, formataddr try: from backports.configparser import ConfigParser except ImportError: from configparser import ConfigParser from isso.compat import text_type as str logger = logging.getLogger("isso") def timedelta(string): """ Parse :param string: into :class:`datetime.timedelta`, you can use any (logical) combination of Nw, Nd, Nh and Nm, e.g. `1h30m` for 1 hour, 30 minutes or `3w` for 3 weeks. Raises a ValueError if the input is invalid/unparseable. >>> print(timedelta("3w")) 21 days, 0:00:00 >>> print(timedelta("3w 12h 57m")) 21 days, 12:57:00 >>> print(timedelta("1h30m37s")) 1:30:37 >>> print(timedelta("1asdf3w")) Traceback (most recent call last): ... ValueError: invalid human-readable timedelta """ keys = ["weeks", "days", "hours", "minutes", "seconds"] regex = "".join(["((?P<%s>\\d+)%s ?)?" % (k, k[0]) for k in keys]) kwargs = {} for k, v in re.match(regex, string).groupdict(default="0").items(): kwargs[k] = int(v) rv = datetime.timedelta(**kwargs) if rv == datetime.timedelta(): raise ValueError("invalid human-readable timedelta") return datetime.timedelta(**kwargs) class Section(object): """A wrapper around :class:`IssoParser` that returns a partial configuration section object. >>> conf = new({"foo": {"bar": "spam"}}) >>> section = conf.section("foo") >>> conf.get("foo", "bar") == section.get("bar") True """ def __init__(self, conf, section): self.conf = conf self.section = section def get(self, key): return self.conf.get(self.section, key) def getint(self, key): return self.conf.getint(self.section, key) def getlist(self, key): return self.conf.getlist(self.section, key) def getiter(self, key): return self.conf.getiter(self.section, key) def getboolean(self, key): return self.conf.getboolean(self.section, key) class IssoParser(ConfigParser): """Parse INI-style configuration with some modifications for Isso. * parse human-readable timedelta such as "15m" as "15 minutes" * handle indented lines as "lists" """ def getint(self, section, key): try: delta = timedelta(self.get(section, key)) except ValueError: return super(IssoParser, self).getint(section, key) else: try: return int(delta.total_seconds()) except AttributeError: return int(delta.total_seconds()) def getlist(self, section, key): return list(map(str.strip, self.get(section, key).split(','))) def getiter(self, section, key): for item in map(str.strip, self.get(section, key).split('\n')): if item: yield item def section(self, section): return Section(self, section) def new(options=None): cp = IssoParser(allow_no_value=True) if options: cp.read_dict(options) return cp def load(default, user=None): # return set of (section, option) def setify(cp): return set((section, option) for section in cp.sections() for option in cp.options(section)) parser = new() parser.read(default) a = setify(parser) if user: parser.read(user) for item in setify(parser).difference(a): logger.warn("no such option: [%s] %s", *item) if item in (("server", "host"), ("server", "port")): logger.warn("use `listen = http://$host:$port` instead") if item == ("smtp", "ssl"): logger.warn("use `security = none | starttls | ssl` instead") if item == ("general", "session-key"): logger.info("Your `session-key` has been stored in the " "database itself, this option is now unused") if not parseaddr(parser.get("smtp", "from"))[0]: parser.set("smtp", "from", formataddr(("Ich schrei sonst!", parser.get("smtp", "from")))) return parser