view configmix/ini.py @ 284:4aaf74858d07

Some links to AWS docu into the aws moduleSome links to AWS docu into the aws moduleSome links to AWS docu into the aws moduleSome links to AWS docu into the aws moduleSome links to AWS docu into the aws moduleSome links to AWS docu into the aws moduleSome links to AWS docu into the aws moduleSome links to AWS docu into the aws module
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 07 Dec 2020 01:59:11 +0100
parents 2cfd670281ae
children eed16a1ec8f3
line wrap: on
line source

# -*- coding: utf-8 -*-
# :-
# :Copyright: (c) 2015-2020, Franz Glasner. All rights reserved.
# :License:   3-clause BSD. See LICENSE.txt for details.
# :-
"""Read INI-style configuration files.

"""

from __future__ import division, absolute_import, print_function


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


import sys
import os
import io
import locale
try:
    from configparser import ConfigParser, NoSectionError, NoOptionError
    # SafeConfigParser is deprecated in Python 3.2+ (together with "readfp()")
    if hasattr(ConfigParser, "read_file"):
        _ConfigParserBase = ConfigParser
    else:
        from configparser import SafeConfigParser
        _ConfigParserBase = SafeConfigParser
except ImportError:
    from ConfigParser import SafeConfigParser, NoSectionError, NoOptionError
    _ConfigParserBase = SafeConfigParser
try:
    from collections import OrderedDict as DictImpl
except ImportError:
    try:
        from ordereddict import OrderedDict as DictImpl
    except ImportError:
        DictImpl = dict

from .compat import u, u2fs


class INIConfigParser(_ConfigParserBase):

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

    """

    def __init__(self, filename, executable=None, encoding=None):
        _ConfigParserBase.__init__(self)
        if executable is None:
            executable = sys.argv[0]
        filename = u(filename, locale.getpreferredencoding())
        executable = u(executable, locale.getpreferredencoding())
        self.executable = os.path.normpath(os.path.abspath(executable))
        if encoding is None:
            encoding = locale.getpreferredencoding()
        self.encoding = encoding
        with io.open(u2fs(filename), mode="rt", encoding=self.encoding) as cf:
            self.read_file(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):
        """Not implemented. Use :meth:`read_file` instead."""
        raise NotImplementedError("use `read_file()' instead")

    def readfp(self, fp, filename):
        """Compatibility for older Python versions.

        Use :meth:`.read_file` instead.

        """
        return self.read_file(fp, filename)

    def read_file(self, fp, filename):
        """Read from a file-like object `fp`.

        The `fp` argument must be iterable (Python 3.2+) or have a
        `readline()` method (Python 2, <3.2).

        """
        if hasattr(self, "filename"):
            raise RuntimeError("already initialized")
        filename = os.path.normpath(os.path.abspath(filename))
        filename = u(filename, 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))
        if hasattr(_ConfigParserBase, "read_file"):
            _ConfigParserBase.read_file(self, fp, source=filename)
        else:
            _ConfigParserBase.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 :meth:`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 itemsx(self, section, options):
        """Get all the options given in `options` of section `section`.
        Fetch them with :meth:`getx` in the order given.

        Return a list of ``(name, value)`` pairs for each option in
        `options` in the given `section`.

        """
        d = []
        for option in options:
            try:
                val = self.getx(section, option)
            except (NoSectionError, NoOptionError):
                pass
            else:
                d.append((option, val, ))
        return d

    def items_as_dictx(self, section, options):
        """Similar to :meth:`itemsx` but return a (possibly ordered)
        dict instead of a list of key-value pairs.

        """
        return DictImpl(self.itemsx(section, options))


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

    Flattens the given sections into the resulting dictionary.

    Then build a tree out of sections which start with any of the `extract`
    content value and a point ``.``.

    The encoding of the file is given in `encoding`.

    """
    conf = DictImpl()
    ini = INIConfigParser(filename, encoding=encoding)
    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
    # try to read "<extract>.xxx" sections as tree
    for treemarker in [e + '.' for e in extract]:
        sections = list(ini.sections())
        sections.sort()
        for section in sections:
            cur_cfg = conf
            if section.startswith(treemarker):
                treestr = section[len(treemarker):]
                for treepart in treestr.split('.'):
                    cur_cfg = cur_cfg.setdefault(treepart, DictImpl())
                for option in ini.options(section):
                    value = ini.getx(section, option)
                    cur_cfg[option] = value
    return conf