Source code for objconfig.reader.ini

r"""
This is a port of zend-config to Python

Some idioms of PHP are still employed, but where possible I have Pythonized it

IGNORE:
    Author: Asher Wolfstein Copyright 2017
    Blog: http://wunk.me/
    E-Mail: asherwunk@gmail.com
    Twitter: https://twitter.com/asherwolfstein Send Me Some Love!
    Package Homepage: http://wunk.me/programming-projects/objconfig-python/
    GitHub: http://github.com/asherwunk/objconfig for the source repository
    DevPost: https://devpost.com/software/objconfig
    Buy Me A Coffee: https://ko-fi.com/A18224XC
    Support Me On Patreon: https://www.patreon.com/asherwolfstein
IGNORE

Following is the header as given in zend-config::

    /**
     * Zend Framework (http://framework.zend.com/)
     *
     * @link      http://github.com/zendframework/zf2 for the
     *            canonical source repository
     * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc.
     *            (http://www.zend.com)
     * @license   http://framework.zend.com/license/new-bsd New BSD License
     */
"""

from configparser import ConfigParser
import configparser
from objconfig.reader import ReaderInterface
from objconfig.exception import RuntimeException
from objconfig.util import array_merge_recursive
import os
import collections


[docs]class Ini(ReaderInterface): r""" Following is the class documentation as given in zend-config:: /** * INI config reader. */ """
[docs] @staticmethod def configParserToDict(config): """ Transform a configparser object into a dictionary. """ ret = {} for section in config.items(): ret[section[0]] = {} for key in config[section[0]]: ret[section[0]][key] = config[section[0]][key] return ret
def __init__(self, nestSeparator='.'): """ Initialize nestSeparator and directory """ r""" Following is the header as given in zend-config:: /** * Separator for nesting levels of configuration data identifiers. * * @var string */ """ self.nestSeparator = nestSeparator r""" Following is the header as given in zend-config:: /** * Directory of the file to process. * * @var string */ """ self.directory = ''
[docs] def setNestSeparator(self, separator): r""" Following is the header as given in zend-config:: /** * Set nest separator. * * @param string $separator * @return self */ """ self.nestSeparator = separator return self
[docs] def getNestSeparator(self): r""" Following is the header as given in zend-config:: /** * Get nest separator. * * @return string */ """ return self.nestSeparator
[docs] def fromFile(self, filename): r""" Following is the header as given in zend-config:: /** * fromFile(): defined by Reader interface. * * @see ReaderInterface::fromFile() * @param string $filename * @return array * @throws Exception\RuntimeException */ """ if not os.path.isfile(filename) or not os.access(filename, os.R_OK): raise RuntimeException("Ini: File %s Doesn't Exist or Not Readable" % filename) self.directory = os.path.dirname(filename.rstrip(os.sep)) or '.' ini = ConfigParser() try: inicontent = '' with open(filename, "r") as file: for line in file: if "@include" in line: include = line.split("=")[1] if include[0] != '/': include = os.path.join(self.directory, include) if (not os.path.isfile(os.path.join(self.directory, include)) or not os.access(os.path.join(self.directory, include), os.R_OK)): raise RuntimeException("Ini: File %s Doesn't Exist or Not Readable" % os.path.join(self.directory, include)) with open(include, "r") as includedfile: for includedline in includedfile: inicontent += includedline else: inicontent += line ini.read_string(inicontent) except configparser.Error as e: raise RuntimeException("Ini: Error Reading INI file \"%s\": %s" % (filename, e)) ret = Ini.configParserToDict(ini) return self.process(ret)
[docs] def fromString(self, string): r""" Following is the header as given in zend-config:: /** * fromString(): defined by Reader interface. * * @param string $string * @return array|bool * @throws Exception\RuntimeException */ """ if not string: return {} self.directory = None ini = ConfigParser() if "@include" in string: raise RuntimeException("Ini: Cannot Process @include When Reading From String") try: ini.read_string(string) except configparser.Error as e: raise RuntimeException("Ini: Error Reading INI string: %s" % e) ret = Ini.configParserToDict(ini) return self.process(ret)
[docs] def process(self, data): r""" Following is the header as given in zend-config:: /** * Process data from the parsed ini file. * * @param array $data * @return array */ """ ret = {} for section, value in data.items(): if isinstance(value, dict): if self.nestSeparator in section: sections = section.split(self.nestSeparator) ret = array_merge_recursive(ret, self.buildNestedSection(sections, value)) else: ret[section] = self.processSection(value) else: self.processKey(section, value, ret) return ret
[docs] def buildNestedSection(self, sections, value): r""" Following is the header as given in zend-config:: /** * Process a nested section * * @param array $sections * @param mixed $value * @return array */ """ if not len(sections): return self.processSection(value) sections = collections.deque(sections) nestedSection = {} first = sections.popleft() nestedSection[first] = self.buildNestedSection(sections, value) return nestedSection
[docs] def processSection(self, section): r""" Following is the header as given in zend-config:: /** * Process a section. * * @param array $section * @return array */ """ ret = {} for key, value in section.items(): self.processKey(key, value, ret) return ret
[docs] def processKey(self, key, value, ret): r""" Following is the header as given in zend-config:: /** * Process a key. * * @param string $key * @param string $value * @param array $config * @return array * @throws Exception\RuntimeException */ """ if self.nestSeparator in key: pieces = key.split(self.nestSeparator, 1) if not len(pieces[0]) or not len(pieces[1]): raise RuntimeException("Ini: Invalid Key \"%s\"" % key) elif not pieces[0] in ret: if pieces[0] == '0' and ret: ret = {pieces[0]: ret} else: ret[pieces[0]] = {} elif ((isinstance(ret[pieces[0]], dict) and pieces[1] in ret[pieces[0]]) or (not isinstance(ret[pieces[0]], dict) and ret[pieces[0]])): raise RuntimeException("Ini: Cannot Create Sub-Key for \"%s\" : \"%s\", as key already exists" % (pieces[0], pieces[1])) self.processKey(pieces[1], value, ret[pieces[0]]) else: ret[key] = value