source: rtems-tools/rtemstoolkit/macros.py @ 3618a62

Last change on this file since 3618a62 was bf58911, checked in by Chris Johns <chrisj@…>, on Oct 24, 2017 at 10:35:21 AM

tester: Refactor to use INI format files for BSP configurations.

  • Add support for user condfigurations files with the --user-config.
  • Add support for a $HOME/.rtemstesterrc for a user configuration.

Closes #3204.

  • Property mode set to 100644
File size: 19.4 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2010-2016 Chris Johns (chrisj@rtems.org)
4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions are met:
10#
11# 1. Redistributions of source code must retain the above copyright notice,
12# this list of conditions and the following disclaimer.
13#
14# 2. Redistributions in binary form must reproduce the above copyright notice,
15# this list of conditions and the following disclaimer in the documentation
16# and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29#
30
31#
32# Macro tables.
33#
34
35from __future__ import print_function
36
37import copy
38import inspect
39import re
40import os
41import string
42
43#
44# Support to handle use in a package and as a unit test.
45# If there is a better way to let us know.
46#
47try:
48    from . import error
49    from . import log
50    from . import path
51except (ValueError, SystemError):
52    import error
53    import log
54    import path
55
56#
57# Macro tables
58#
59class macros:
60
61    class macro_iterator:
62        def __init__(self, keys):
63            self.keys = keys
64            self.index = 0
65
66        def __iter__(self):
67            return self
68
69        def __next__(self):
70            if self.index < len(self.keys):
71                key = self.keys[self.index]
72                self.index += 1
73                return key
74            raise StopIteration
75
76        def iterkeys(self):
77            return self.keys
78
79    def _unicode_to_str(self, us):
80        try:
81            if type(us) == unicode:
82                return us.encode('ascii', 'replace')
83        except:
84            pass
85        try:
86            if type(us) == bytes:
87                return us.encode('ascii', 'replace')
88        except:
89            pass
90        return us
91
92    def __init__(self, name = None, original = None, rtdir = '.'):
93        self.files = []
94        self.macro_filter = re.compile(r'%{[^}]+}')
95        if original is None:
96            self.macros = {}
97            self.read_maps = []
98            self.read_map_locked = False
99            self.write_map = 'global'
100            self.rtpath = path.abspath(path.dirname(inspect.getfile(macros)))
101            if path.dirname(self.rtpath).endswith('/share/rtems'):
102                self.prefix = path.dirname(self.rtpath)[:-len('/share/rtems')]
103            else:
104                self.prefix = '.'
105            self.macros['global'] = {}
106            self.macros['global']['nil'] = ('none', 'none', '')
107            self.macros['global']['_cwd'] = ('dir',
108                                             'required',
109                                             path.abspath(os.getcwd()))
110            self.macros['global']['_prefix'] = ('dir', 'required', self.prefix)
111            self.macros['global']['_rtdir'] = ('dir',
112                                               'required',
113                                               path.abspath(self.expand(rtdir)))
114            self.macros['global']['_rttop'] = ('dir', 'required', self.prefix)
115        else:
116            self.macros = {}
117            for m in original.macros:
118                if m not in self.macros:
119                    self.macros[m] = {}
120                for k in original.macros[m]:
121                    self.macros[m][k] = copy.copy(original.macros[m][k])
122            self.read_maps = sorted(copy.copy(original.read_maps))
123            self.read_map_locked = copy.copy(original.read_map_locked)
124            self.write_map = copy.copy(original.write_map)
125        if name is not None:
126            self.load(name)
127
128    def __copy__(self):
129        return macros(original = self)
130
131    def __str__(self):
132        text_len = 80
133        text = ''
134        for f in self.files:
135            text += '> %s%s' % (f, os.linesep)
136        for map in self.macros:
137            rm = '-'
138            for rmap in self.read_maps:
139                if rmap[4:] == '___%s' % (map):
140                    if self.read_map_locked:
141                        rm = 'R[%s]' % (rmap[:4])
142                    else:
143                        rm = 'r[%s]' % (rmap[:4])
144                    break
145            if map == self.write_map:
146                wm = 'w'
147            else:
148                wm = '-'
149            text += '[%s] %s,%s%s' % (map, wm, rm, os.linesep)
150            for k in sorted(self.macros[map].keys()):
151                d = self.macros[map][k]
152                text += " %s:%s '%s'%s '%s'%s" % \
153                    (k, ' ' * (20 - len(k)),
154                     d[0], ' ' * (8 - len(d[0])),
155                     d[1], ' ' * (10 - len(d[1])))
156                if len(d[2]) == 0:
157                    text += "''%s" % (os.linesep)
158                else:
159                    if '\n' in d[2]:
160                        text += "'''"
161                    else:
162                        text += "'"
163                indent = False
164                ds = d[2].split('\n')
165                lc = 0
166                for l in ds:
167                    lc += 1
168                    while len(l):
169                        if indent:
170                            text += ' %21s %10s %12s' % (' ', ' ', ' ')
171                        text += l[0:text_len]
172                        l = l[text_len:]
173                        if len(l):
174                            text += ' \\'
175                        elif lc == len(ds):
176                            if len(ds) > 1:
177                                text += "'''"
178                            else:
179                                text += "'"
180                        text += '%s' % (os.linesep)
181                        indent = True
182        return text
183
184    def __iter__(self):
185        return macros.macro_iterator(self.keys())
186
187    def __getitem__(self, key):
188        macro = self.get(key)
189        if macro is None or macro[1] == 'undefine':
190            raise IndexError('key: %s' % (key))
191        return macro[2]
192
193    def __setitem__(self, key, value):
194        key = self._unicode_to_str(key)
195        if type(key) is not str:
196            raise TypeError('bad key type (want str): %s' % (type(key)))
197        if type(value) is not tuple:
198            value = self._unicode_to_str(value)
199        if type(value) is str:
200            value = ('none', 'none', value)
201        if type(value) is not tuple:
202            raise TypeError('bad value type (want tuple): %s' % (type(value)))
203        if len(value) != 3:
204            raise TypeError('bad value tuple (len not 3): %d' % (len(value)))
205        value = (self._unicode_to_str(value[0]),
206                 self._unicode_to_str(value[1]),
207                 self._unicode_to_str(value[2]))
208        if type(value[0]) is not str:
209            raise TypeError('bad value tuple type field: %s' % (type(value[0])))
210        if type(value[1]) is not str:
211            raise TypeError('bad value tuple attrib field: %s' % (type(value[1])))
212        if type(value[2]) is not str:
213            raise TypeError('bad value tuple value field: %s' % (type(value[2])))
214        if value[0] not in ['none', 'triplet', 'dir', 'file', 'exe']:
215            raise TypeError('bad value tuple (type field): %s' % (value[0]))
216        if value[1] not in ['none', 'optional', 'required',
217                            'override', 'undefine', 'convert']:
218            raise TypeError('bad value tuple (attrib field): %s' % (value[1]))
219        if value[1] == 'convert':
220            value = self.expand(value)
221        self.macros[self.write_map][self.key_filter(key)] = value
222
223    def __delitem__(self, key):
224        self.undefine(key)
225
226    def __contains__(self, key):
227        return self.has_key(key)
228
229    def __len__(self):
230        return len(list(self.keys()))
231
232    def keys(self):
233        keys = list(self.macros['global'].keys())
234        for rm in self.get_read_maps():
235            for mk in self.macros[rm]:
236                if self.macros[rm][mk][1] == 'undefine':
237                    if mk in keys:
238                        keys.remove(mk)
239                else:
240                    keys.append(mk)
241        return sorted(set(keys))
242
243    def has_key(self, key):
244        if type(key) is not str:
245            raise TypeError('bad key type (want str): %s' % (type(key)))
246        if self.key_filter(key) not in list(self.keys()):
247            return False
248        return True
249
250    def maps(self):
251        return self.macros.keys()
252
253    def get_read_maps(self):
254        return [rm[7:] for rm in self.read_maps]
255
256    def key_filter(self, key):
257        if key.startswith('%{') and key[-1] is '}':
258            key = key[2:-1]
259        return key.lower()
260
261    def parse(self, lines):
262
263        def _clean(l):
264            if '#' in l:
265                l = l[:l.index('#')]
266            if '\r' in l:
267                l = l[:l.index('r')]
268            if '\n' in l:
269                l = l[:l.index('\n')]
270            return l.strip()
271
272        trace_me = False
273        if trace_me:
274            print('[[[[]]]] parsing macros')
275        orig_macros = copy.copy(self.macros)
276        map = 'global'
277        lc = 0
278        state = 'key'
279        token = ''
280        macro = []
281        for l in lines:
282            lc += 1
283            #print 'l:%s' % (l[:-1])
284            if len(l) == 0:
285                continue
286            l_remaining = l
287            for c in l:
288                if trace_me:
289                    print(']]]]]]]] c:%s(%d) s:%s t:"%s" m:%r M:%s' % \
290                        (c, ord(c), state, token, macro, map))
291                l_remaining = l_remaining[1:]
292                if c is '#' and not state.startswith('value'):
293                    break
294                if c == '\n' or c == '\r':
295                    if not (state is 'key' and len(token) == 0) and \
296                            not state.startswith('value-multiline'):
297                        self.macros = orig_macros
298                        raise error.general('malformed macro line:%d: %s' % (lc, l))
299                if state is 'key':
300                    if c not in string.whitespace:
301                        if c is '[':
302                            state = 'map'
303                        elif c is '%':
304                            state = 'directive'
305                        elif c is ':':
306                            macro += [token]
307                            token = ''
308                            state = 'attribs'
309                        elif c is '#':
310                            break
311                        else:
312                            token += c
313                elif state is 'map':
314                    if c is ']':
315                        if token not in self.macros:
316                            self.macros[token] = {}
317                        map = token
318                        token = ''
319                        state = 'key'
320                    elif c in string.printable and c not in string.whitespace:
321                        token += c
322                    else:
323                        self.macros = orig_macros
324                        raise error.general('invalid macro map:%d: %s' % (lc, l))
325                elif state is 'directive':
326                    if c in string.whitespace:
327                        if token == 'include':
328                            self.load(_clean(l_remaining))
329                            token = ''
330                            state = 'key'
331                            break
332                    elif c in string.printable and c not in string.whitespace:
333                        token += c
334                    else:
335                        self.macros = orig_macros
336                        raise error.general('invalid macro directive:%d: %s' % (lc, l))
337                elif state is 'include':
338                    if c is string.whitespace:
339                        if token == 'include':
340                            state = 'include'
341                    elif c in string.printable and c not in string.whitespace:
342                        token += c
343                    else:
344                        self.macros = orig_macros
345                        raise error.general('invalid macro directive:%d: %s' % (lc, l))
346                elif state is 'attribs':
347                    if c not in string.whitespace:
348                        if c is ',':
349                            macro += [token]
350                            token = ''
351                            if len(macro) == 3:
352                                state = 'value-start'
353                        else:
354                            token += c
355                elif state is 'value-start':
356                    if c is "'":
357                        state = 'value-line-start'
358                elif state is 'value-line-start':
359                    if c is "'":
360                        state = 'value-multiline-start'
361                    else:
362                        state = 'value-line'
363                        token += c
364                elif state is 'value-multiline-start':
365                    if c is "'":
366                        state = 'value-multiline'
367                    else:
368                        macro += [token]
369                        state = 'macro'
370                elif state is 'value-line':
371                    if c is "'":
372                        macro += [token]
373                        state = 'macro'
374                    else:
375                        token += c
376                elif state is 'value-multiline':
377                    if c is "'":
378                        state = 'value-multiline-end'
379                    else:
380                        token += c
381                elif state is 'value-multiline-end':
382                    if c is "'":
383                        state = 'value-multiline-end-end'
384                    else:
385                        state = 'value-multiline'
386                        token += "'" + c
387                elif state is 'value-multiline-end-end':
388                    if c is "'":
389                        macro += [token]
390                        state = 'macro'
391                    else:
392                        state = 'value-multiline'
393                        token += "''" + c
394                else:
395                    self.macros = orig_macros
396                    raise error.internal('bad state: %s' % (state))
397                if state is 'macro':
398                    self.macros[map][macro[0].lower()] = (macro[1], macro[2], macro[3])
399                    macro = []
400                    token = ''
401                    state = 'key'
402
403    def load(self, name):
404        names = self.expand(name).split(':')
405        for n in names:
406            log.trace('opening: %s' % (n))
407            if path.exists(n):
408                try:
409                    mc = open(path.host(n), 'r')
410                    macros = self.parse(mc)
411                    mc.close()
412                    self.files += [n]
413                    return
414                except IOError as err:
415                    pass
416        raise error.general('opening macro file: %s' % \
417                                (path.host(self.expand(name))))
418
419    def get(self, key):
420        if type(key) is not str:
421            raise TypeError('bad key type: %s' % (type(key)))
422        key = self.key_filter(key)
423        for rm in self.get_read_maps():
424            if key in self.macros[rm]:
425                return self.macros[rm][key]
426        if key in self.macros['global']:
427            return self.macros['global'][key]
428        return None
429
430    def get_type(self, key):
431        m = self.get(key)
432        if m is None:
433            return None
434        return m[0]
435
436    def get_attribute(self, key):
437        m = self.get(key)
438        if m is None:
439            return None
440        return m[1]
441
442    def get_value(self, key):
443        m = self.get(key)
444        if m is None:
445            return None
446        return m[2]
447
448    def overridden(self, key):
449        return self.get_attribute(key) == 'override'
450
451    def define(self, key, value = '1'):
452        if type(key) is not str:
453            raise TypeError('bad key type: %s' % (type(key)))
454        self.__setitem__(key, ('none', 'none', value))
455
456    def undefine(self, key):
457        if type(key) is not str:
458            raise TypeError('bad key type: %s' % (type(key)))
459        key = self.key_filter(key)
460        for map in self.macros:
461            if key in self.macros[map]:
462                del self.macros[map][key]
463
464    def expand(self, _str):
465        """Simple basic expander of config file macros."""
466        start_str = _str
467        expanded = True
468        count = 0
469        while expanded:
470            count += 1
471            if count > 1000:
472                raise error.general('expansion looped over 1000 times "%s"' %
473                                    (start_str))
474            expanded = False
475            for m in self.macro_filter.findall(_str):
476                name = m[2:-1]
477                macro = self.get(name)
478                if macro is None:
479                    raise error.general('cannot expand default macro: %s in "%s"' %
480                                        (m, _str))
481                _str = _str.replace(m, macro[2])
482                expanded = True
483        return _str
484
485    def find(self, regex):
486        what = re.compile(regex)
487        keys = []
488        for key in self.keys():
489            if what.match(key):
490                keys += [key]
491        return keys
492
493    def set_read_map(self, _map):
494        if not self.read_map_locked:
495            if _map in self.macros:
496                if _map not in self.get_read_maps():
497                    rm = '%04d___%s' % (len(self.read_maps), _map)
498                    self.read_maps = sorted(self.read_maps + [rm])
499                return True
500        return False
501
502    def unset_read_map(self, _map):
503        if not self.read_map_locked:
504            if _map in self.get_read_maps():
505                for i in range(0, len(self.read_maps)):
506                    if '%04d___%s' % (i, _map) == self.read_maps[i]:
507                        self.read_maps.pop(i)
508                return True
509        return False
510
511    def set_write_map(self, _map, add = False):
512        if _map in self.macros:
513            self.write_map = _map
514            return True
515        elif add:
516            self.write_map = _map
517            self.macros[_map] = {}
518            return True
519        return False
520
521    def unset_write_map(self):
522        self.write_map = 'global'
523        return True
524
525    def lock_read_map(self):
526        self.read_map_locked = True
527
528    def unlock_read_map(self):
529        self.read_map_locked = False
530
531if __name__ == "__main__":
532    import copy
533    import sys
534    print(inspect.getfile(macros))
535    m = macros()
536    d = copy.copy(m)
537    m['test1'] = 'something'
538    if d.has_key('test1'):
539        print('error: copy failed.')
540        sys.exit(1)
541    m.parse("[test]\n" \
542            "test1: none, undefine, ''\n" \
543            "name:  none, override, 'pink'\n")
544    print('set test:', m.set_read_map('test'))
545    if m['name'] != 'pink':
546        print('error: override failed. name is %s' % (m['name']))
547        sys.exit(1)
548    if m.has_key('test1'):
549        print('error: map undefine failed.')
550        sys.exit(1)
551    print('unset test:', m.unset_read_map('test'))
552    print(m)
553    print(m.keys())
Note: See TracBrowser for help on using the repository browser.