OpenCores
URL https://opencores.org/ocsvn/radiohdl/radiohdl/trunk

Subversion Repositories radiohdl

[/] [radiohdl/] [trunk/] [core/] [configfile.py] - Rev 4

Compare with Previous | Blame | View Log

###############################################################################
#
# Copyright (C) 2014-2018
# ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# $Id$
#
###############################################################################
 
"""Class for accessing the values of a configuration file of RadioHDL.
 
   The contents of the configuration file consist of a series of key - value
   pairs. These key - value pairs are read from the file and kept in a
   single dictionary of keys and values that can be accessed via .content.
 
   The format of the configuration file is similar to that of an ini file. For ini
   files Python has the ConfigParser package, but that is not used here because
   we need other parsing rules than the ones implemented in ConfigParser.
   The parsing is done during the allocation of the class.
 
   Like an ini file the configurationfile can contain one or more sections. The
   first section is common, has no header and always included. The specific
   sections have a header that is marked by [section header]. The square brackets
   '[' and ']' are used to identify the section header. If the 'section header' is
   included in the argument 'sections' of the constructor then the keys of that
   section will be included in the dictionary.
 
     'sections' = None  --> ignore fileSections to include all sections in
                            the dict.
     'sections' = []    --> empty list to include only the common first
                            section in the dict.
     'sections' = ['section header' ...] -->
                            dedicated list of one or more section header
                            strings to include these specific sections,
                            and also the common first section, in the
                            dict.
 
   The key and value string in the dictionary file are separated by '='. Hence
   the '=' character can not be used in keys. The '=' can be used in values,
   because subsequent '=' on the same line are part of the value.
   Each key must start on a new line and may not contain whitespace.
   The value string can extend over one or multiple lines.
 
   Comment in line is supported by preceding it with a '#'. The '#' and the
   text after it on the line are stripped. The remainder of the line before
   the '#' is still interpreted.
 
   Example:
     # This is a comment section
     # a key starts on a new line and extends until the '='
     # a key and its values are separated by '='
 
     key=string   # this is a comment and the key is still interpreted
     key=string
     key =string
     key = string
 
     # a key with multiple values in its string
     key = value value value
 
     # how the values are separated depends on the dictionary user
     key = value, value, value
     key = value value
 
     # a key with many values can have its string extend on multiple lines,
     # the newline is replaced by a ' ' and any indent is removed
     key =
     value
     value
     value
 
     # empty lines and spaces are allowed
     key = value
"""
 
import os.path
import re
from collections import OrderedDict
 
__all__ = ['CFG_COMMENT_CHAR', 'CFG_ASSIGNMENT_CHAR', 'ConfigFileException', 'ConfigFile']
 
CFG_COMMENT_CHAR    = '#'
CFG_ASSIGNMENT_CHAR = '='
 
 
class ConfigFileException(Exception):
    "Exception class used in the ConfigFile class"
    pass
 
 
class ConfigFile(object):
    """
    :filename	   The full filename of the configfile. May contain environment variables.
    :sections      Optional. May contain a list of sections names. The key-value pairs in the other sections
                   are ignored.
    :required_keys Optional. May contain a list of all keys that must exist in the configfile. If one or more
                   of those keys is missing in the configfile an exception is raised.
    """
    _CONFIGFILE_ATTRIBUTES = ['_own_attr_', 'filename', 'location', 'sections', 'required_keys',
                              '__content__', 'section_headers', 'unresolved_refs', 'warnings']
 
    def __init__(self, filename, sections=None, required_keys=[]):
        """
        Store the dictionaries from all fileName files in rootDir.
        :raise ConfigFileException
        """
        full_filename = os.path.expanduser(os.path.expandvars(filename))
        if not os.path.isfile(full_filename):
            raise ConfigFileException("configfile '%s' not found" % full_filename)
 
        # Crucial: define dict for storing our (simulated) attributes
        self.__dict__['_own_attr_'] = {}
        self.__content__     = OrderedDict()
 
        self.filename        = os.path.basename(full_filename)
        self.location        = os.path.dirname(full_filename)
        self.sections        = sections
        self.required_keys   = required_keys
        self.section_headers = []
        self.unresolved_refs = []
        self.warnings        = ''
 
        # Try to read the configuration file.
        self._read_file()
 
        # Check if all required keys are available
        for required_key in self.required_keys:
            if required_key not in self.__content__:
                raise ConfigFileException("configfile '%s' missing key '%s'" % (filename, required_key))
 
    def __getattr__(self, name):
        """
        Catch read-access to attributes to fetch values from the content dictionary.
        :raise AtttributeError
        """
        if name in self._CONFIGFILE_ATTRIBUTES:
            return self.__dict__['_own_attr_'][name]
 
        if name in self.__dict__['_own_attr_']['__content__']:
            return self.__dict__['_own_attr_']['__content__'][name]
 
        print('%s object has no attribute %s' % (str(self.__class__.__name__), str(name)))
 
        if not hasattr(self, name):
            raise AttributeError("%r object has no attribute %r" % (self.__class__.__name__, name))
 
        return getattr(self, name)
 
    def __setattr__(self, name, value):
        """
        Catch write-access to attributes to store values in the content dictionary.
        :raise AtttributeError
        """
        if name in self._CONFIGFILE_ATTRIBUTES:
            self.__dict__['_own_attr_'][name] = value
        elif name in self.__dict__['_own_attr_']['__content__']:
            self.__dict__['_own_attr_']['__content__'][name] = value
        else:
            if not hasattr(self, name):
                raise AttributeError("%r object has no attribute %r" % (self.__class__.__name__, name))
            setattr(self, name, value)
 
    def __getitem__(self, key):
        "Also allow access to the information as item."
        return self.__getattr__(key)
 
    def __setitem__(self, key, value):
        "Also allow access to the information as item."
        self.__setattr__(key, value)
 
    def _add_kv_pair(self, key, value, include_in_section):
        """
        Internal function for adding a key-value pair in a neat way.
        """
        # print("add_kv_pair: key={}, value={}\n".format(key, value))
        if not include_in_section:
            return
        if key.find(' ') > 0:
            self.warnings += "Error: Key may not contain spaces: file '{}/{}', key '{}'"\
                             .format(self.location, self.filename, key)
        elif key != '':
            self.__content__[key] = value.strip()       # Update dict with key and value
 
    def _read_file(self):
        """
        Read the dictionary information the filePathName file.
        The dictionary will contain all key-value pairs as well as a
        'section_headers' key that contains all the sections found in the file
        (regardless if the keys of that section where included in the dict or not).
        :raise ConfigFileException
        """
        include_section = True      # default include all sections
        key             = ''
        value           = ''
        linenr          = 0
        with open("{}/{}".format(self.location, self.filename), 'r') as configfile:
            for line in configfile:
                linenr += 1
                ln = line.split(CFG_COMMENT_CHAR, 1)[0]           # Strip comment from line
                if len(ln) == 0:
                    continue
                section_begin = ln.find('[')                       # Search for [section] header in this line
                section_end   = ln.find(']')
                # section MUST start at first character of the line
                if section_begin == 0 and section_end > 0:
                    self._add_kv_pair(key, value, include_section)  # complete action on current key
                    key = ''
                    value = ''
                    # administrate new section
                    section_header = ln[1:section_end].strip()    # new section header
                    self.section_headers.append(section_header)
                    include_section = True                        # default include this new section
                    if self.sections is not None:
                        if section_header not in self.sections:
                            include_section = False               # skip this section
                else:
                    key_end = ln.find(CFG_ASSIGNMENT_CHAR)        # Search for key in this line
                    if key_end > 0:
                        self._add_kv_pair(key, value, include_section)  # complete action on current key
                        key   = ln[0:key_end].strip()             # start with new key
                        value = ln[key_end+1:].strip()            # and new value
                    else:
                        # no assignment char found, append this line to the current value
                        value += ' '                              # replace newline by space to separate values
                        value += ln.strip()                       # append value
                        if value.strip() != '' and key == '':
                            self.warnings += "Warning: value without a key: file '{}/{}', line {} ({})"\
                                             .format(self.location, self.filename, linenr, value)
                            value = ''
                self._add_kv_pair(key, value, include_section)  # complete action on current key
 
        if self.warnings != '':
            raise ConfigFileException(self.warnings)
 
    @property
    def content(self):
        # print(self.__content__)
        "The content of the configfile as ordered dictionary."
        return self.__content__
 
    @property
    def ID(self):
        "Returns uniq ID (string) to identify this particular file. Fullfilename is used."
        return "{}/{}".format(self.location, self.filename)
 
    def resolve_key_references(self):
        """
        Replaces in all values the references to keys (<key>) with the value of that key.
        Note that this operation is irreversable.
        """
        ref_pattern = r"<(.+?)>"
        prog = re.compile(ref_pattern)
        # print('__content__={}'.format(self.__content__.items()))
        # loop over all items of the dict
        for key, value in list(self.__content__.items()):
            # any reference in the value?
            matchlist = list(set(prog.findall(value)))
            for reference in matchlist:
                if reference in list(self.__content__.keys()):
                    self.__content__[key] = re.sub("<{}>".format(reference), self.__content__[reference], value)
                    value = self.__content__[key]
                else:
                    self.unresolved_refs.append("<{}>".format(reference))
        return len(self.unresolved_refs) == 0
 
    def get_value(self, key, must_exist=False):
        """
        Get the value of a key. If the key does not exist and that is allowed 'None' is returned, in case the
        key should have been there an exception is raised.
        """
        if key in self.__content__:
            return self.__content__[key]
 
        if must_exist:
            raise ConfigFileException("Key '%s' does not exist in configfile %s/%s." % (key, self.location, self.filename))
 
        return None
 

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.