|
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
|
|
|
|