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

Subversion Repositories radiohdl

[/] [radiohdl/] [trunk/] [core/] [configfile.py] - Blame information for rev 4

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 4 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$
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 required_key not in self.__content__:
143
                raise ConfigFileException("configfile '%s' missing key '%s'" % (filename, required_key))
144
 
145
    def __getattr__(self, name):
146
        """
147
        Catch read-access to attributes to fetch values from the content dictionary.
148
        :raise AtttributeError
149
        """
150
        if name in self._CONFIGFILE_ATTRIBUTES:
151
            return self.__dict__['_own_attr_'][name]
152
 
153
        if name in self.__dict__['_own_attr_']['__content__']:
154
            return self.__dict__['_own_attr_']['__content__'][name]
155
 
156
        print('%s object has no attribute %s' % (str(self.__class__.__name__), str(name)))
157
 
158
        if not hasattr(self, name):
159
            raise AttributeError("%r object has no attribute %r" % (self.__class__.__name__, name))
160
 
161
        return getattr(self, name)
162
 
163
    def __setattr__(self, name, value):
164
        """
165
        Catch write-access to attributes to store values in the content dictionary.
166
        :raise AtttributeError
167
        """
168
        if name in self._CONFIGFILE_ATTRIBUTES:
169
            self.__dict__['_own_attr_'][name] = value
170
        elif name in self.__dict__['_own_attr_']['__content__']:
171
            self.__dict__['_own_attr_']['__content__'][name] = value
172
        else:
173
            if not hasattr(self, name):
174
                raise AttributeError("%r object has no attribute %r" % (self.__class__.__name__, name))
175
            setattr(self, name, value)
176
 
177
    def __getitem__(self, key):
178
        "Also allow access to the information as item."
179
        return self.__getattr__(key)
180
 
181
    def __setitem__(self, key, value):
182
        "Also allow access to the information as item."
183
        self.__setattr__(key, value)
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
        # print("add_kv_pair: key={}, value={}\n".format(key, value))
190
        if not include_in_section:
191
            return
192
        if key.find(' ') > 0:
193
            self.warnings += "Error: Key may not contain spaces: file '{}/{}', key '{}'"\
194
                             .format(self.location, self.filename, key)
195
        elif key != '':
196
            self.__content__[key] = value.strip()       # Update dict with key and value
197
 
198
    def _read_file(self):
199
        """
200
        Read the dictionary information the filePathName file.
201
        The dictionary will contain all key-value pairs as well as a
202
        'section_headers' key that contains all the sections found in the file
203
        (regardless if the keys of that section where included in the dict or not).
204
        :raise ConfigFileException
205
        """
206
        include_section = True      # default include all sections
207
        key             = ''
208
        value           = ''
209
        linenr          = 0
210
        with open("{}/{}".format(self.location, self.filename), 'r') as configfile:
211
            for line in configfile:
212
                linenr += 1
213
                ln = line.split(CFG_COMMENT_CHAR, 1)[0]           # Strip comment from line
214
                if len(ln) == 0:
215
                    continue
216
                section_begin = ln.find('[')                       # Search for [section] header in this line
217
                section_end   = ln.find(']')
218
                # section MUST start at first character of the line
219
                if section_begin == 0 and section_end > 0:
220
                    self._add_kv_pair(key, value, include_section)  # complete action on current key
221
                    key = ''
222
                    value = ''
223
                    # administrate new section
224
                    section_header = ln[1:section_end].strip()    # new section header
225
                    self.section_headers.append(section_header)
226
                    include_section = True                        # default include this new section
227
                    if self.sections is not None:
228
                        if section_header not in self.sections:
229
                            include_section = False               # skip this section
230
                else:
231
                    key_end = ln.find(CFG_ASSIGNMENT_CHAR)        # Search for key in this line
232
                    if key_end > 0:
233
                        self._add_kv_pair(key, value, include_section)  # complete action on current key
234
                        key   = ln[0:key_end].strip()             # start with new key
235
                        value = ln[key_end+1:].strip()            # and new value
236
                    else:
237
                        # no assignment char found, append this line to the current value
238
                        value += ' '                              # replace newline by space to separate values
239
                        value += ln.strip()                       # append value
240
                        if value.strip() != '' and key == '':
241
                            self.warnings += "Warning: value without a key: file '{}/{}', line {} ({})"\
242
                                             .format(self.location, self.filename, linenr, value)
243
                            value = ''
244
                self._add_kv_pair(key, value, include_section)  # complete action on current key
245
 
246
        if self.warnings != '':
247
            raise ConfigFileException(self.warnings)
248
 
249
    @property
250
    def content(self):
251
        # print(self.__content__)
252
        "The content of the configfile as ordered dictionary."
253
        return self.__content__
254
 
255
    @property
256
    def ID(self):
257
        "Returns uniq ID (string) to identify this particular file. Fullfilename is used."
258
        return "{}/{}".format(self.location, self.filename)
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
        # print('__content__={}'.format(self.__content__.items()))
268
        # loop over all items of the dict
269
        for key, value in list(self.__content__.items()):
270
            # any reference in the value?
271
            matchlist = list(set(prog.findall(value)))
272
            for reference in matchlist:
273
                if reference in list(self.__content__.keys()):
274
                    self.__content__[key] = re.sub("<{}>".format(reference), self.__content__[reference], value)
275
                    value = self.__content__[key]
276
                else:
277
                    self.unresolved_refs.append("<{}>".format(reference))
278
        return len(self.unresolved_refs) == 0
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

powered by: WebSVN 2.1.0

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