view configmix/ini.py @ 21:ce290b10dac5

Better Py2/Py3 compatibility: mark some strings explicitly as Unicode
author Franz Glasner <f.glasner@feldmann-mg.com>
date Thu, 10 Mar 2016 13:28:09 +0100
parents 9bdc4e421415
children 7c7955da42ab
line wrap: on
line source

# -*- coding: utf-8 -*-
r"""Read INI-style configuration files.

"""

from __future__ import division, absolute_import, print_function

import sys
import os
import io
import locale
try:
    from configparser import SafeConfigParser, NoSectionError, NoOptionError
except ImportError:
    from ConfigParser import SafeConfigParser, NoSectionError, NoOptionError
try:
    from collections import OrderedDict as DictImpl
except ImportError:
    try:
        from ordereddict import OrderedDict as DictImpl
    except ImportError:
        DictImpl = dict

from .compat import PY2, u


__all__ = ["INIConfigParser", "NoSectionError", "NoOptionError",
           "load"]


class INIConfigParser(SafeConfigParser):

    """A case sensitive config parser that returns all-unicode string
    values.

    """

    def __init__(self, filename, executable=None, encoding=None):
        SafeConfigParser.__init__(self)
        if executable is None:
            executable = sys.argv[0]
        if PY2:
            if isinstance(filename, str):
                filename = filename.decode(locale.getpreferredencoding())
            if isinstance(executable, str):
                executable = executable.decode(locale.getpreferredencoding())
        self.executable = os.path.normpath(os.path.abspath(executable))
        if encoding is None:
            encoding = locale.getpreferredencoding()
        self.encoding = encoding
        with io.open(filename, mode="rt", encoding=self.encoding) as cf:
            self.readfp(cf, filename)

    def optionxform(self, option):
        return option

    def get_path_list(self, section, option):
        v = self.get(section, option)
        return v.split(os.pathsep)

    def read(self, filenames):
        raise NotImplementedError("use `readfp()' instead")

    def readfp(self, fp, filename):
        if hasattr(self, "filename"):
            raise RuntimeError("already initialized")
        filename = os.path.normpath(os.path.abspath(filename))
        if PY2:
            if isinstance(filename, str):
                filename = filename.decode(locale.getpreferredencoding())
        self.set(None, u("self"), filename)
        self.set(None, u("here"), os.path.dirname(filename))
        self.set(None, u("root"), os.path.dirname(self.executable))
        SafeConfigParser.readfp(self, fp, filename=filename)
        self.filename = filename
        self.root = os.path.dirname(self.executable)

    def getx(self, section, option):
        """Extended get() with some automatic type conversion support.

        Default: Fetch as string (like `get()`).

        If annotated with ``:bool:`` fetch as bool, if annotated with
        ``:int:`` fetch as int, if annotated with ``:float:`` fetch as
        float.

        """
        v = self.get(section, option)
        if v.startswith(u(":bool:")):
            v = v[6:].lower()
            if v not in self._BOOL_CVT:
                raise ValueError("Not a boolean: %r" % v)
            return self._BOOL_CVT[v]
        elif v.startswith(u(":int:")):
            return int(v[5:], 0)
        elif v.startswith(u(":float:")):
            return float(v[7:])
        else:
            return v

    _BOOL_CVT = {u('1'): True,
                 u('yes'): True,
                 u('true'): True,
                 u('on'): True,
                 u('0'): False,
                 u('no'): False,
                 u('false'): False,
                 u('off'): False}


def load(filename, extract=["config"]):
    """Load a single INI file and read/interpolate the sections given in
    `extract`.

    Flattens the given sections into the resulting dictionary.

    """
    conf = DictImpl()
    ini = INIConfigParser(filename)
    for sect in extract:
        try:
            cfg = ini.options(sect)
        except NoSectionError:
            pass
        else:
            for option in cfg:
                value = ini.getx(sect, option)
                conf[option] = value
    return conf