source: rtems-source-builder/source-builder/sb/macros.py

Last change on this file was 650c6f9, checked in by Chris Johns <chrisj@…>, on 08/25/20 at 11:21:50

sb: Use shebang env python

Closes #4037

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