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