URL
https://opencores.org/ocsvn/radiohdl/radiohdl/trunk
Subversion Repositories radiohdl
[/] [radiohdl/] [trunk/] [core/] [configfile.py] - Rev 6
Go to most recent revision | 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
Go to most recent revision | Compare with Previous | Blame | View Log