source: rtems-tools/rtemstoolkit/macros.py @ 4001a74

4.11
Last change on this file since 4001a74 was 4001a74, checked in by Chris Johns <chrisj@…>, on Mar 2, 2016 at 9:54:06 AM

Update rtems-tool to support Python 2 and 3.

Add solaris and netbsd.

Close #2619.

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