]> git.proxmox.com Git - ceph.git/blob - ceph/doc/scripts/gen_state_diagram.py
add subtree-ish sources for 12.0.3
[ceph.git] / ceph / doc / scripts / gen_state_diagram.py
1 #!/usr/bin/env python
2 import re
3 import sys
4
5
6 def do_filter(generator):
7 return acc_lines(remove_multiline_comments(to_char(remove_single_line_comments(generator))))
8
9
10 def acc_lines(generator):
11 current = ""
12 for i in generator:
13 current += i
14 if i == ';' or \
15 i == '{' or \
16 i == '}':
17 yield current.lstrip("\n")
18 current = ""
19
20
21 def to_char(generator):
22 for line in generator:
23 for char in line:
24 if char is not '\n':
25 yield char
26 else:
27 yield ' '
28
29
30 def remove_single_line_comments(generator):
31 for i in generator:
32 if len(i) and i[0] == '#':
33 continue
34 yield re.sub(r'//.*', '', i)
35
36
37 def remove_multiline_comments(generator):
38 saw = ""
39 in_comment = False
40 for char in generator:
41 if in_comment:
42 if saw is "*":
43 if char is "/":
44 in_comment = False
45 saw = ""
46 if char is "*":
47 saw = "*"
48 continue
49 if saw is "/":
50 if char is '*':
51 in_comment = True
52 saw = ""
53 continue
54 else:
55 yield saw
56 saw = ""
57 if char is '/':
58 saw = "/"
59 continue
60 yield char
61
62
63 class StateMachineRenderer(object):
64 def __init__(self):
65 self.states = {} # state -> parent
66 self.machines = {} # state-> initial
67 self.edges = {} # event -> [(state, state)]
68
69 self.context = [] # [(context, depth_encountered)]
70 self.context_depth = 0
71 self.state_contents = {}
72 self.subgraphnum = 0
73 self.clusterlabel = {}
74
75 def __str__(self):
76 return "-------------------\n\nstates: %s\n\n machines: %s\n\n edges: %s\n\n context %s\n\n state_contents %s\n\n--------------------" % (
77 self.states,
78 self.machines,
79 self.edges,
80 self.context,
81 self.state_contents
82 )
83
84 def read_input(self, input_lines):
85 for line in input_lines:
86 self.get_state(line)
87 self.get_event(line)
88 self.get_context(line)
89
90 def get_context(self, line):
91 match = re.search(r"(\w+::)*::(?P<tag>\w+)::\w+\(const (?P<event>\w+)",
92 line)
93 if match is not None:
94 self.context.append((match.group('tag'), self.context_depth, match.group('event')))
95 if '{' in line:
96 self.context_depth += 1
97 if '}' in line:
98 self.context_depth -= 1
99 while len(self.context) and self.context[-1][1] == self.context_depth:
100 self.context.pop()
101
102 def get_state(self, line):
103 if "boost::statechart::state_machine" in line:
104 tokens = re.search(
105 r"boost::statechart::state_machine<\s*(\w*),\s*(\w*)\s*>",
106 line)
107 if tokens is None:
108 raise "Error: malformed state_machine line: " + line
109 self.machines[tokens.group(1)] = tokens.group(2)
110 self.context.append((tokens.group(1), self.context_depth, ""))
111 return
112 if "boost::statechart::state" in line:
113 tokens = re.search(
114 r"boost::statechart::state<\s*(\w*),\s*(\w*)\s*,?\s*(\w*)\s*>",
115 line)
116 if tokens is None:
117 raise "Error: malformed state line: " + line
118 self.states[tokens.group(1)] = tokens.group(2)
119 if tokens.group(2) not in self.state_contents.keys():
120 self.state_contents[tokens.group(2)] = []
121 self.state_contents[tokens.group(2)].append(tokens.group(1))
122 if tokens.group(3) is not "":
123 self.machines[tokens.group(1)] = tokens.group(3)
124 self.context.append((tokens.group(1), self.context_depth, ""))
125 return
126
127 def get_event(self, line):
128 if "boost::statechart::transition" in line:
129 for i in re.finditer(r'boost::statechart::transition<\s*([\w:]*)\s*,\s*(\w*)\s*>',
130 line):
131 if i.group(1) not in self.edges.keys():
132 self.edges[i.group(1)] = []
133 if len(self.context) is 0:
134 raise "no context at line: " + line
135 self.edges[i.group(1)].append((self.context[-1][0], i.group(2)))
136 i = re.search("return\s+transit<\s*(\w*)\s*>()", line)
137 if i is not None:
138 if len(self.context) is 0:
139 raise "no context at line: " + line
140 if self.context[-1][2] is "":
141 raise "no event in context at line: " + line
142 if self.context[-1][2] not in self.edges.keys():
143 self.edges[self.context[-1][2]] = []
144 self.edges[self.context[-1][2]].append((self.context[-1][0], i.group(1)))
145
146 def emit_dot(self):
147 top_level = []
148 for state in self.machines.keys():
149 if state not in self.states.keys():
150 top_level.append(state)
151 print >> sys.stderr, "Top Level States: ", str(top_level)
152 print """digraph G {"""
153 print '\tsize="7,7"'
154 print """\tcompound=true;"""
155 for i in self.emit_state(top_level[0]):
156 print '\t' + i
157 for i in self.edges.keys():
158 for j in self.emit_event(i):
159 print j
160 print """}"""
161
162 def emit_state(self, state):
163 if state in self.state_contents.keys():
164 self.clusterlabel[state] = "cluster%s" % (str(self.subgraphnum),)
165 yield "subgraph cluster%s {" % (str(self.subgraphnum),)
166 self.subgraphnum += 1
167 yield """\tlabel = "%s";""" % (state,)
168 yield """\tcolor = "blue";"""
169 for j in self.state_contents[state]:
170 for i in self.emit_state(j):
171 yield "\t"+i
172 yield "}"
173 else:
174 found = False
175 for (k, v) in self.machines.items():
176 if v == state:
177 yield state+"[shape=Mdiamond];"
178 found = True
179 break
180 if not found:
181 yield state+";"
182
183 def emit_event(self, event):
184 def append(app):
185 retval = "["
186 for i in app:
187 retval += (i + ",")
188 retval += "]"
189 return retval
190 for (fro, to) in self.edges[event]:
191 appendix = ['label="%s"' % (event,)]
192 if fro in self.machines.keys():
193 appendix.append("ltail=%s" % (self.clusterlabel[fro],))
194 while fro in self.machines.keys():
195 fro = self.machines[fro]
196 if to in self.machines.keys():
197 appendix.append("lhead=%s" % (self.clusterlabel[to],))
198 while to in self.machines.keys():
199 to = self.machines[to]
200 yield("%s -> %s %s;" % (fro, to, append(appendix)))
201
202
203 INPUT_GENERATOR = do_filter(sys.stdin.xreadlines())
204 RENDERER = StateMachineRenderer()
205 RENDERER.read_input(INPUT_GENERATOR)
206 RENDERER.emit_dot()