configparser.py 11.1 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
#
Pawel Szostek's avatar
Pawel Szostek committed
3 4
# Copyright (c) 2013 CERN
# Author: Pawel Szostek (pawel.szostek@cern.ch)
5
#
Pawel Szostek's avatar
Pawel Szostek committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# This file is part of Hdlmake.
#
# Hdlmake 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.
#
# Hdlmake 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 Hdlmake.  If not, see <http://www.gnu.org/licenses/>.
#
21

22 23
from __future__ import print_function
import logging
Pawel Szostek's avatar
Pawel Szostek committed
24 25 26 27
import sys
import StringIO
import contextlib

28

Pawel Szostek's avatar
Pawel Szostek committed
29 30 31 32 33 34 35 36
@contextlib.contextmanager
def stdoutIO(stdout=None):
    old = sys.stdout
    if stdout is None:
        stdout = StringIO.StringIO()
    sys.stdout = stdout
    yield stdout
    sys.stdout = old
37

38

39 40 41 42 43
class ConfigParser(object):
    """Class for parsing python configuration files

    Case1: Normal usage
    >>> f = open("test.py", "w")
44
    >>> f.write('modules = {"local":"/path/to/local", "svn":"path/to/svn"}; ')
45 46 47 48 49 50 51 52
    >>> f.write('fetchto = ".."' )
    >>> f.close()
    >>> p = ConfigParser()
    >>> p.add_option("modules", type={})
    >>> p.add_option("fetchto", type='')
    >>> p.add_config_file("test.py")
    >>> p.parse()
    {'modules': {'svn': 'path/to/svn', 'local': '/path/to/local'}, 'fetchto': '..'}
53

54 55 56 57 58 59 60 61 62 63
    Case2: Default value and lack of a variable
    >>> f = open("test.py", "w")
    >>> f.write('a="123"')
    >>> f.close()
    >>> p = ConfigParser()
    >>> p.add_option("a", type='')
    >>> p.add_option("b", type='', default='borsuk')
    >>> p.add_config_file("test.py")
    >>> p.parse()
    {'a': '123', 'b': 'borsuk'}
64

65 66 67 68 69 70 71 72 73 74
    Case3: Multiple types for a variable
    >>> f = open("test.py", "w")
    >>> f.write('a=[1,2,3]')
    >>> f.close()
    >>> p = ConfigParser()
    >>> p.add_option("a", type=1, default=12)
    >>> p.add_type("a", type=[])
    >>> p.add_config_file("test.py")
    >>> p.parse()
    {'a': [1, 2, 3]}
75

76 77 78 79 80 81 82 83 84 85 86 87 88
    Case4: Unrecognized options
    >>> f = open("test.py", "w")
    >>> f.write('a = 123')
    >>> f.close()
    >>> p = ConfigParser()
    >>> p.add_option("b", type='')
    >>> p.add_config_file("test.py")
    >>> p.parse()
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "configparser.py", line 107, in parse
        raise NameError("Unrecognized option: " + key)
    NameError: Unrecognized option: a
89

90 91 92 93 94 95 96 97 98 99 100 101 102
    Case5: Invalid parameter type
    >>> f = open("test.py","w")
    >>> f.write('a="123"')
    >>> f.close()
    >>> p = ConfigParser()
    >>> p.add_option("a", type=0)
    >>> p.add_config_file("test.py")
    >>> p.parse()
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "configparser.py", line 110, in parse
        raise RuntimeError("Given option: "+str(type(val))+" doesn't match specified types:"+str(opt.types))
    RuntimeError: Given option: <type 'str'> doesn't match specified types:[<type 'int'>]
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

    Case6:
    >>> f = open("test.py","w")
    >>> f.write('a={"zupa":1}')
    >>> f.close()
    >>> p = ConfigParser()
    >>> p.add_option("a", type={})
    >>> p.add_allowed_key("a", "zupa")
    >>> p.add_config_file("test.py")
    >>> p.parse()
    {'a': {'zupa': 1}}

    Case7
    >>> f = open("test.py","w")
    >>> f.write('a={"kot":1}')
    >>> f.close()
    >>> p = ConfigParser()
    >>> p.add_option("a", type={})
    >>> p.add_allowed_key("a", "kniaz")
    >>> p.add_config_file("test.py")
    >>> p.parse()
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "configparser.py", line 184, in parse
        raise RuntimeError("Encountered unallowed key: " +key+ " for options '"+opt_name+"'")
    RuntimeError: Encountered unallowed key: kot for options 'a'

130 131 132 133
    Cleanup:
    >>> import os
    >>> os.remove("test.py")
    """
134

135 136 137 138 139
    class Option:
        def __init__(self, name, **others):
            self.name = name
            self.keys = []
            self.types = []
140
            self.help = ""
Pawel Szostek's avatar
Pawel Szostek committed
141
            self.arbitrary_code = ""
142
            self.global_code = ""
143 144 145 146 147 148 149

            for key in others:
                if key == "help":
                    self.help = others["help"]
                elif key == "default":
                    self.default = others["default"]
                elif key == "type":
150
                    self.add_type(type_obj=others["type"])
151 152 153 154 155 156
                else:
                    raise ValueError("Option not recognized: " + key)

        def add_type(self, type_obj):
            self.types.append(type(type_obj))

157
    def __init__(self, description=None):
158
        if description is not None:
159
            if not isinstance(description, str):
160 161
                raise ValueError("Description should be a string!")
        self.description = description
Pawel Szostek's avatar
Pawel Szostek committed
162
        self.options = []
Pawel Szostek's avatar
Pawel Szostek committed
163
        self.arbitrary_code = ""
164
        self.config_file = None
165

Pawel Szostek's avatar
Pawel Szostek committed
166 167 168 169 170 171 172 173
    def __setitem__(self, name, value):
        if name in self.__names():
            filter(lambda x: x.name == name, self.options)[0] = value
        else:
            self.options.append(value)

    def __getitem__(self, name):
        if name in self.__names():
174
            return [x for x in self.options if x is not None and x.name == name][0]
Pawel Szostek's avatar
Pawel Szostek committed
175
        else:
176
            raise RuntimeError("No such option as " + str(name))
Pawel Szostek's avatar
Pawel Szostek committed
177

178
    def help(self):
179
        print("Variables with special meaning for Hdlmake:")
Pawel Szostek's avatar
Pawel Szostek committed
180
        for opt in self.options:
181
            if opt is None:
182
                print("")
Pawel Szostek's avatar
Pawel Szostek committed
183 184
                continue

185
            line = '  {0:15}; {1:29}; {2:45}{3}{4:10}'
186 187 188 189
            try:
                tmp_def = opt.default
                if tmp_def == "":
                    tmp_def = '""'
190 191 192
                line = line.format(opt.name, str(opt.types), opt.help, ', default=', tmp_def)
            except AttributeError:  # no default value
                line = line.format(opt.name, str(opt.types), opt.help, "", "")
193
            print(line)
194

195
    def add_option(self, name, **others):
Pawel Szostek's avatar
Pawel Szostek committed
196
        if name in self.__names():
197
            raise ValueError("Option already added: " + name)
Pawel Szostek's avatar
Pawel Szostek committed
198
        self.options.append(ConfigParser.Option(name, **others))
199 200

    def add_type(self, name, type):
Pawel Szostek's avatar
Pawel Szostek committed
201
        if name not in self.__names():
202
            raise RuntimeError("Can't add type to a non-existing option")
Pawel Szostek's avatar
Pawel Szostek committed
203 204 205 206
        self[name].add_type(type)

    def add_delimiter(self):
        self.options.append(None)
207

208
    def add_allowed_key(self, name, key):
209
        if not isinstance(key, str):
210 211
            raise ValueError("Allowed key must be a string")
        try:
Pawel Szostek's avatar
Pawel Szostek committed
212
            self[name].allowed_keys.append(key)
213
        except AttributeError:
Pawel Szostek's avatar
Pawel Szostek committed
214
            if type(dict()) not in self[name].types:
215
                raise RuntimeError("Allowing a key makes sense for dictionaries only")
Pawel Szostek's avatar
Pawel Szostek committed
216
            self[name].allowed_keys = [key]
217

Pawel Szostek's avatar
Pawel Szostek committed
218
        self[name].allowed_keys.append(key)
219

220
    def add_config_file(self, config_file):
221 222
        if self.config_file is not None:
            raise RuntimeError("Config file should be added only once")
223

224 225 226 227 228
        import os
        if not os.path.exists(config_file):
            raise RuntimeError("Config file doesn't exists: " + config_file)
        self.config_file = config_file
        return
229

230
    def add_arbitrary_code(self, code):
Pawel Szostek's avatar
Pawel Szostek committed
231
        self.arbitrary_code += code + '\n'
232

Pawel Szostek's avatar
Pawel Szostek committed
233
    def __names(self):
234
        return [o.name for o in self.options if o is not None]
Pawel Szostek's avatar
Pawel Szostek committed
235

236
    def parse(self, verbose=False, extra_context=None):
237
        assert isinstance(extra_context, dict) or extra_context is None
238 239 240
        options = {}
        ret = {}

241 242
        if self.config_file is not None:
            with open(self.config_file, "r") as config_file:
243
                content = config_file.readlines()
244
                content = ''.join(content)
245
        else:
Pawel Szostek's avatar
Pawel Szostek committed
246
            content = ''
Pawel Szostek's avatar
Pawel Szostek committed
247
        content = self.arbitrary_code + '\n' + content
Pawel Szostek's avatar
Pawel Szostek committed
248 249 250 251 252 253 254 255

        #now the trick:
        #I take the arbitrary code and parse it
        #the values are not important, but thanks to it I can check
        #if a variable came from the arbitrary code.
        #This is important because in the manifests only certain group
        #of variables is allowed. In arbitrary code all of them can be used.
        arbitrary_options = {}
256
        import sys
257
        try:
Pawel Szostek's avatar
Pawel Szostek committed
258
            with stdoutIO() as s:
259
                exec(self.arbitrary_code, extra_context, arbitrary_options)
Pawel Szostek's avatar
Pawel Szostek committed
260 261 262
            printed = s.getvalue()
            if printed:
                print(printed)
263
        except SyntaxError as e:
264
            logging.error("Invalid syntax in the arbitraty code:\n" + str(e))
265
            quit()
266
        except:
267 268
            logging.error("Unexpected error while parsing arbitrary code:")
            print(str(sys.exc_info()[0])+':'+str(sys.exc_info()[1]))
269
            quit()
270 271

        try:
Pawel Szostek's avatar
Pawel Szostek committed
272
            with stdoutIO() as s:
273
                exec(content, extra_context, options)
Pawel Szostek's avatar
Pawel Szostek committed
274 275
            printed = s.getvalue()
            if len(printed) > 0:
276
                logging.info("The manifest inside " + self.config_file + " tried to print something:")
Pawel Szostek's avatar
Pawel Szostek committed
277
                for line in printed.split('\n'):
278
                    print("> " + line)
Pawel Szostek's avatar
Pawel Szostek committed
279
            #print "out:", s.getvalue()
280
        except SyntaxError as e:
281
            logging.error("Invalid syntax in the manifest file " + self.config_file + ":\n" + str(e))
282
            logging.error(content)
283
            quit()
284
        except:
285
            logging.error("Encountered unexpected error while parsing " + self.config_file)
286
            logging.error(content)
287
            print(str(sys.exc_info()[0]) + ':' + str(sys.exc_info()[1]))
288
            raise
289

290
        for opt_name, val in list(options.items()):  # check delivered options
291
            if opt_name.startswith('__'):
292
                continue
Pawel Szostek's avatar
Pawel Szostek committed
293
            if opt_name not in self.__names():
Pawel Szostek's avatar
Pawel Szostek committed
294
                if opt_name in arbitrary_options:
295
                    continue  # finish processing of this variable here
296
                else:
297
                    ret[opt_name] = val
298
                    logging.warning("New custom variable found: %s (=%s).\n" % (opt_name, val))
299
                    continue
Pawel Szostek's avatar
Pawel Szostek committed
300
            opt = self[opt_name]
301
            if type(val) not in opt.types:
302
                raise RuntimeError("Given option: %s doesn't match specified types: %s" % (str(type(val)), str(opt.types)))
303
            ret[opt_name] = val
304
#            print("Opt_name ", opt_name)
305 306 307
            if type(val) == type(dict()):
                try:
                    for key in val:
Pawel Szostek's avatar
Pawel Szostek committed
308
                        if key not in self[opt_name].allowed_keys:
309 310
                            raise RuntimeError("Encountered unallowed key: %s for option '%s'" % (key, opt_name))
                except AttributeError:  # no allowed_keys member - don't perform any check
311 312
                    pass

313
        for opt in self.options:  # set values for not listed items with defaults
314
            try:
315 316
                if opt.name not in ret:
                    ret[opt.name] = opt.default
317
            except AttributeError:  # no default value in the option
318
                pass
319 320
        return ret

321

322 323 324
def _test():
    import doctest
    doctest.testmod()
325

326
if __name__ == "__main__":
327
    _test()