Passed
Push — master ( ab513a...1713a6 )
by Peter
05:21 queued 03:17
created

notations_builder   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 209
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 136
dl 0
loc 209
rs 8.2857
c 0
b 0
f 0
wmc 39
1
'''
2
    A builder that creates several python config files from central JSON
3
    config files, the so-called 'notations' files. Each notation file
4
    describes all the relevant semantical rules for the particular graph type.
5
    The Python derivates are generated in a way that they can be smoothly used
6
    for correctness and consistency checks.
7
'''
8
9
from SCons.Script import * 
10
import json, pprint
11
12
def generate_graphml_keys(notations):
13
    '''
14
        Derive the GraphML preamble from our notations file. This is cool,
15
        because our GraphML import / export magically matches to the notations file.
16
17
        GraphML allows to define extensions as part of the document, by having
18
        <key> elements that describe the extensions. Later <data> elements
19
        can refer to these keys.
20
21
        Our GraphML export behaves nicely and always adds this preamble, so that
22
        other GraphML tools can render the values nicely. For this reason,
23
        the 'graphml_keys' list is generated. It is just an ugly collection of XML
24
        definitions, which means that Marcus violates his own understand of 
25
        beautiful code ... 
26
27
        Our GraphML import does not rely on the existence of this preamble, but
28
        simply wants to know if a given GraphML <data> element refers to a valid key. 
29
        For this reason, the 'graphml_graph_data' and 'graphml_node_data' list is generated.
30
        It tells the import code if a graph / node element in the input XML is allowed
31
        to have a particulary named data element.
32
    '''
33
    graphml_keys = {}
34
    graphml_graph_data = {}
35
    graphml_node_data = {}
36
37
    def generate_key_xml(name, kind, default, for_what='node'):
38
        return \
39
            '        <key id="%s" for="%s" attr.name="%s" attr.type="%s">\n' \
40
            '            <default>%s</default>\n' \
41
            '        </key>' % (name, for_what, name, kind, default,)
42
43
44
    for notation in notations:
45
        notation_kind = notation['kind']
46
        graphml_graph_data[notation_kind] = set(['kind'])
47
        graphml_node_data[notation_kind] = set(['id','kind','x','y'])
48
        properties    = set()
49
        all_keys      = [
50
            generate_key_xml('id', 'string', '0'),
51
            generate_key_xml('kind', 'string', 'node'),
52
            generate_key_xml('x', 'long', '0'),
53
            generate_key_xml('y', 'long', '0'),
54
            generate_key_xml('kind', 'string', 'faulttree', 'graph')
55
        ]
56
57
        for node_kind, node in notation['nodes'].items():
58
            for property_name, propertie in node.get('properties', {}).items():
59
                if property_name in properties:
60
                    continue
61
                else:
62
                    properties.add(property_name)
63
64
                property_default = propertie.get('default', '')
65
                property_kind    = propertie['kind']
66
                key              = None
67
                keys             = None
68
69
                if property_kind in {'text', 'textfield'}:
70
                    key = generate_key_xml(property_name, 'string', property_default if propertie != 'name' else 'Node')
71
                    graphml_node_data[notation_kind].add(property_name)
72
                elif property_kind == 'compound':
73
                    parts_index    = property_default[0]
74
                    compound_parts = propertie['parts']
75
76
                    keys = [
77
                        generate_key_xml(property_name,          'string', property_default[1]),
78
                        generate_key_xml(property_name + 'Kind', 'string', compound_parts[parts_index]['partName']),
79
                    ]
80
                    graphml_node_data[notation_kind].add(property_name)
81
                elif property_kind == 'bool':
82
                    key = generate_key_xml(property_name, 'boolean', 'true' if property_default else 'false')
83
                    graphml_node_data[notation_kind].add(property_name)
84
                elif property_kind == 'choice':
85
                    index = propertie['values'].index(property_default)
86
                    property_default = property_default[index]
87
                    key = generate_key_xml(property_name, 'string', property_default)
88
                    graphml_node_data[notation_kind].add(property_name)
89
                elif property_kind == 'epsilon':
90
                    keys = [
91
                        generate_key_xml(property_name,             'double', property_default[0]),
92
                        generate_key_xml(property_name + 'Epsilon', 'double', property_default[1])
93
                    ]
94
                    graphml_node_data[notation_kind].add(property_name)
95
                elif property_kind in {'numeric', 'range'}:
96
                    kind = 'long' if propertie.get('step', 1) == 1 else 'double'
97
98
                    if property_name != 'missionTime':
99
                        key = generate_key_xml(property_name, kind, property_default)
100
                        graphml_node_data[notation_kind].add(property_name)
101
                    else:
102
                        key = generate_key_xml(property_name, kind, property_default, 'graph')
103
                        graphml_graph_data[notation_kind].add(property_name)
104
                elif property_kind == 'transfer':
105
                    key = generate_key_xml(property_name, 'string', '')
106
                    graphml_node_data[notation_kind].add(property_name)
107
108
                if key is not None:
109
                    all_keys.append(key)
110
                else:
111
                    all_keys.extend(keys)
112
113
        graphml_keys[notation_kind] = '\n'.join(all_keys)
114
115
    return graphml_keys, graphml_graph_data, graphml_node_data
116
117
def extend(target, source, *others, **options):
118
    all_sources = (source,) + others
119
    deep = options.get('deep', False)
120
121
    for other in all_sources:
122
        if not deep:
123
            # perform classical dict update, since nested dicts are not used
124
            target.update(other)
125
            continue
126
127
        for key, value in other.items():
128
            if key in target and isinstance(target[key], dict) and isinstance(value, dict):
129
                target[key] = extend({}, target[key], other[key], deep=True)
130
            else:
131
                target[key] = value
132
133
    return target
134
135
def generate_choices(notations):
136
    return [(notation['kind'], notation['name']) for notation in notations]
137
138
def generate_node_choices(notations):
139
    node_choices = []
140
141
    for notation in notations:
142
        nodes = notation['nodes']
143
        node_category = (notation['name'],)
144
        node_category_choices = ()
145
146
        for node_kind, node in nodes.items():
147
            node_category_choices += ((node_kind, node['nodeDisplayName']),)
148
149
        node_category += (node_category_choices,)
150
        node_choices.append(node_category)
151
152
    return node_choices
153
154
def inherit(node_name, node, nodes, node_cache):
155
    inherits_from = node.get('inherits')
156
157
    if not inherits_from:
158
        node_cache[node_name] = node
159
        return node
160
161
    elif inherits_from not in node_cache:
162
        inherit(inherits_from, nodes[inherits_from], nodes, node_cache)
163
164
    resolved = extend({}, node_cache[inherits_from], node, deep=True)
165
    node_cache[node_name] = resolved
166
167
    return resolved
168
169
def resolve_inheritance(notations):
170
    for notation in notations:
171
        notation[u'edges'] = notation.get(u'edges', {})
172
        nodes = notation['nodes']
173
        node_cache = {}
174
175
        for node_name, node in nodes.items():
176
            nodes[node_name] = inherit(node_name, node, nodes, node_cache)
177
178
def notations(target, source, env):
179
    '''
180
        The central build task for the Python notations file equivalents.
181
    '''
182
    notations = []
183
184
    for input_file in source:
185
        with open(str(input_file)) as handle:
186
            notations.append(json.loads(handle.read()))
187
    resolve_inheritance(notations)
188
189
190
    with open(str(target[0]), 'w') as out:
191
        out.write('# DO NOT EDIT! This file is auto-generated by "setup.py build"\n')
192
        out.write('notations = ')
193
        pprint.pprint(notations, out)
194
        out.write('\nby_kind = {notation[\'kind\']: notation for notation in notations}\n')
195
        out.write('choices = ')
196
        pprint.pprint(generate_choices(notations), out)
197
        out.write('\nnode_choices = ')
198
        pprint.pprint(generate_node_choices(notations), out)
199
        key_xml, graph_data, node_data = generate_graphml_keys(notations)
200
        out.write('\ngraphml_keys = ')
201
        pprint.pprint(key_xml, out)
202
        out.write('\ngraphml_graph_data = ')
203
        pprint.pprint(graph_data, out)
204
        out.write('\ngraphml_node_data = ')
205
        pprint.pprint(node_data, out)
206
        out.write('\n# END OF GENERATED CONTENT')
207
208
notationsbuilder = Builder(action = notations)
209