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

Subversion Repositories radiohdl

[/] [radiohdl/] [trunk/] [base/] [configfile.py] - Blame information for rev 2

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 2 danv
###############################################################################
2
#
3
# Copyright (C) 2014-2018
4
# ASTRON (Netherlands Institute for Radio Astronomy) <http://www.astron.nl/>
5
# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
6
#
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
#
20
# $Id: configfile.py 18870 2018-09-06 13:39:53Z overeem $
21
#
22
###############################################################################
23
 
24
"""Class for accessing the values of a configuration file of RadioHDL.
25
 
26
   The contents of the configuration file consist of a series of key - value
27
   pairs. These key - value pairs are read from the file and kept in a
28
   single dictionary of keys and values that can be accessed via .content.
29
 
30
   The format of the configuration file is similar to that of an ini file. For ini
31
   files Python has the ConfigParser package, but that is not used here because
32
   we need other parsing rules than the ones implemented in ConfigParser.
33
   The parsing is done during the allocation of the class.
34
 
35
   Like an ini file the configurationfile can contain one or more sections. The
36
   first section is common, has no header and always included. The specific
37
   sections have a header that is marked by [section header]. The square brackets
38
   '[' and ']' are used to identify the section header. If the 'section header' is
39
   included in the argument 'sections' of the constructor then the keys of that
40
   section will be included in the dictionary.
41
 
42
     'sections' = None  --> ignore fileSections to include all sections in
43
                            the dict.
44
     'sections' = []    --> empty list to include only the common first
45
                            section in the dict.
46
     'sections' = ['section header' ...] -->
47
                            dedicated list of one or more section header
48
                            strings to include these specific sections,
49
                            and also the common first section, in the
50
                            dict.
51
 
52
   The key and value string in the dictionary file are separated by '='. Hence
53
   the '=' character can not be used in keys. The '=' can be used in values,
54
   because subsequent '=' on the same line are part of the value.
55
   Each key must start on a new line and may not contain whitespace.
56
   The value string can extend over one or multiple lines.
57
 
58
   Comment in line is supported by preceding it with a '#'. The '#' and the
59
   text after it on the line are stripped. The remainder of the line before
60
   the '#' is still interpreted.
61
 
62
   Example:
63
     # This is a comment section
64
     # a key starts on a new line and extends until the '='
65
     # a key and its values are separated by '='
66
 
67
     key=string   # this is a comment and the key is still interpreted
68
     key=string
69
     key =string
70
     key = string
71
 
72
     # a key with multiple values in its string
73
     key = value value value
74
 
75
     # how the values are separated depends on the dictionary user
76
     key = value, value, value
77
     key = value value
78
 
79
     # a key with many values can have its string extend on multiple lines,
80
     # the newline is replaced by a ' ' and any indent is removed
81
     key =
82
     value
83
     value
84
     value
85
 
86
     # empty lines and spaces are allowed
87
     key = value
88
"""
89
 
90
import os.path
91
import re
92
from collections import OrderedDict
93
 
94
__all__ = ['CFG_COMMENT_CHAR', 'CFG_ASSIGNMENT_CHAR', 'ConfigFileException', 'ConfigFile' ]
95
 
96
CFG_COMMENT_CHAR    = '#'
97
CFG_ASSIGNMENT_CHAR = '='
98
 
99
 
100
class ConfigFileException(Exception):
101
    "Exception class used in the ConfigFile class"
102
    pass
103
 
104
 
105
class ConfigFile(object):
106
    """
107
    :filename      The full filename of the configfile. May contain environment variables.
108
    :sections      Optional. May contain a list of sections names. The key-value pairs in the other sections
109
                   are ignored.
110
    :required_keys Optional. May contain a list of all keys that must exist in the configfile. If one or more
111
                   of those keys is missing in the configfile an exception is raised.
112
    """
113
    _CONFIGFILE_ATTRIBUTES = [ '_own_attr_', 'filename', 'location', 'sections', 'required_keys',
114
                               '__content__', 'section_headers', 'unresolved_refs', 'warnings' ]
115
 
116
    def __init__(self, filename, sections=None, required_keys=[]):
117
        """
118
        Store the dictionaries from all fileName files in rootDir.
119
        :raise ConfigFileException
120
        """
121
        full_filename = os.path.expanduser(os.path.expandvars(filename))
122
        if not os.path.isfile(full_filename):
123
            raise ConfigFileException ("configfile '%s' not found" % full_filename)
124
 
125
        # Crucial: define dict for storing our (simulated) attributes
126
        self.__dict__['_own_attr_'] = {}
127
        self.__content__     = OrderedDict()
128
 
129
        self.filename        = os.path.basename(full_filename)
130
        self.location        = os.path.dirname(full_filename)
131
        self.sections        = sections
132
        self.required_keys   = required_keys
133
        self.section_headers = []
134
        self.unresolved_refs = []
135
        self.warnings        = ''
136
 
137
        # Try to read the configuration file.
138
        self._read_file()
139
 
140
        # Check if all required keys are available
141
        for required_key in self.required_keys:
142
            if not required_key in self.__content__:
143
                raise ConfigFileException("configfile '%s' missing key '%s'" % (filename, required_key))
144
 
145
 
146
    def __getattr__(self, name):
147
        """
148
        Catch read-access to attributes to fetch values from the content dictionary.
149
        :raise AtttributeError
150
        """
151
        if name in self._CONFIGFILE_ATTRIBUTES:
152
            return self.__dict__['_own_attr_'][name]
153
 
154
        if name in self.__dict__['_own_attr_']['__content__']:
155
            return self.__dict__['_own_attr_']['__content__'][name]
156
 
157
        if not hasattr(self, name):
158
            raise AttributeError("%r object has no attribute %r" % (self.__class__.__name__, name))
159
        return getattr(self, name)
160
 
161
 
162
    def __setattr__(self, name, value):
163
        """
164
        Catch write-access to attributes to store values in the content dictionary.
165
        :raise AtttributeError
166
        """
167
        if name in self._CONFIGFILE_ATTRIBUTES:
168
            self.__dict__['_own_attr_'][name] = value
169
        elif name in self.__dict__['_own_attr_']['__content__']:
170
            self.__dict__['_own_attr_']['__content__'][name] = value
171
        else:
172
            if not hasattr(self, name):
173
                raise AttributeError("%r object has no attribute %r" % (self.__class__.__name__, name))
174
            setattr(self, name, value)
175
 
176
    def __getitem__(self, key):
177
        "Also allow access to the information as item."
178
        return self.__getattr__(key)
179
 
180
    def __setitem__(self, key, value):
181
        "Also allow access to the information as item."
182
        self.__setattr__(key, value)
183
 
184
 
185
    def _add_kv_pair(self, key, value, include_in_section):
186
        """
187
        Internal function for adding a key-value pair in a neat way.
188
        """
189
        if not include_in_section:
190
            return
191
        if key.find(' ') > 0:
192
            self.warnings += "Error: Key may not contain spaces: file '{}/{}', key '{}'"\
193
                             .format(self.location, self.filename, key)
194
        elif key != '':
195
            self.__content__[key] = value.strip()       # Update dict with key and value
196
 
197
    def _read_file(self):
198
        """
199
        Read the dictionary information the filePathName file.
200
        The dictionary will contain all key-value pairs as well as a
201
        'section_headers' key that contains all the sections found in the file
202
        (regardless if the keys of that section where included in the dict or not).
203
        :raise ConfigFileException
204
        """
205
        include_section = True      # default include all sections
206
        key             = ''
207
        value           = ''
208
        linenr          = 0
209
        with open("{}/{}".format(self.location, self.filename), 'r') as configfile:
210
            for line in configfile:
211
                linenr += 1
212
                ln = line.split(CFG_COMMENT_CHAR, 1)[0]           # Strip comment from line
213
                if len(ln) == 0:
214
                    continue
215
                section_begin= ln.find('[')                       # Search for [section] header in this line
216
                section_end  = ln.find(']')
217
                # section MUST start at first character of the line
218
                if section_begin==0 and section_end>0:
219
                    self._add_kv_pair(key, value, include_section) # complete action on current key
220
                    key = ''
221
                    value = ''
222
                    # administrate new section
223
                    section_header = ln[1:section_end].strip()    # new section header
224
                    self.section_headers.append(section_header)
225
                    include_section = True                        # default include this new section
226
                    if self.sections!=None:
227
                        if section_header not in self.sections:
228
                            include_section = False               # skip this section
229
                else:
230
                    key_end = ln.find(CFG_ASSIGNMENT_CHAR)        # Search for key in this line
231
                    if key_end>0:
232
                        self._add_kv_pair(key, value, include_section) # complete action on current key
233
                        key   = ln[0:key_end].strip()             # start with new key
234
                        value = ln[key_end+1:].strip()            # and new value
235
                    else:
236
                        # no assignment char found, append this line to the current value
237
                        value += ' '                              # replace newline by space to separate values
238
                        value += ln.strip()                       # append value
239
                        if value.strip() != '' and key == '':
240
                            self.warnings += "Warning: value without a key: file '{}/{}', line {} ({})"\
241
                                             .format(self.location, self.filename, linenr, value)
242
                            value = ''
243
                self._add_kv_pair(key, value, include_section) # complete action on current key
244
 
245
        if self.warnings != '':
246
            raise ConfigFileException(self.warnings)
247
 
248
 
249
    @property
250
    def content(self):
251
        "The content of the configfile as ordered dictionary."
252
        return self.__content__
253
 
254
    @property
255
    def ID(self):
256
        "Returns uniq ID (string) to identify this particular file. Fullfilename is used."
257
        return "{}/{}".format(self.location, self.filename)
258
 
259
 
260
    def resolve_key_references(self):
261
        """
262
        Replaces in all values the references to keys (<key>) with the value of that key.
263
        Note that this operation is irreversable.
264
        """
265
        ref_pattern = r"<(.+?)>"
266
        prog = re.compile(ref_pattern)
267
        # loop over all items of the dict
268
        for key, value in self.__content__.items():
269
            # any reference in the value?
270
            matchlist = list(set(prog.findall(value)))
271
            for reference in matchlist:
272
                if reference in self.__content__.keys():
273
                    self.__content__[key] = re.sub("<{}>".format(reference), self.__content__[reference], value)
274
                    value = self.__content__[key]
275
                else:
276
                    self.unresolved_refs.append("<{}>".format(reference))
277
        return len(self.unresolved_refs) == 0
278
 
279
 
280
    def get_value(self, key, must_exist=False):
281
        """
282
        Get the value of a key. If the key does not exist and that is allowed 'None' is returned, in case the
283
        key should have been there an exception is raised.
284
        """
285
        if key in self.__content__:
286
            return self.__content__[key]
287
 
288
        if must_exist:
289
            raise ConfigFileException("Key '%s' does not exist in configfile %s/%s." % (key, self.location, self.filename))
290
 
291
        return None
292
 

powered by: WebSVN 2.1.0

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