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