source: rtems-tools/rtemstoolkit/macros.py @ 2b27eec

Last change on this file since 2b27eec was 2b27eec, checked in by Chris Johns <chrisj@…>, on Jun 6, 2019 at 10:51:31 AM

rtemstoolkit/macros: Improve the macro output.

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