source: rtems-tools/rtemstoolkit/macros.py @ 7e5cdea

5
Last change on this file since 7e5cdea was 7e5cdea, checked in by Chris Johns <chrisj@…>, on 11/23/18 at 04:02:52

rtemstoolkit: Add unit testing for the python modules

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