source: rtems-source-builder/source-builder/sb/macros.py @ 9b54dd8

5
Last change on this file since 9b54dd8 was 9b54dd8, checked in by Chris Johns <chrisj@…>, on 10/27/17 at 06:22:12

sb: Add a defined check.

Updates #3210.

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