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