Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / 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         previous_line = None
86         for line in input_lines:
87             self.get_state(line)
88             self.get_event(line)
89             # pass two lines at a time to get the context so that regexes can
90             # match on split signatures
91             self.get_context(line, previous_line)
92             previous_line = line
93
94     def get_context(self, line, previous_line):
95         match = re.search(r"(\w+::)*::(?P<tag>\w+)::\w+\(const (?P<event>\w+)", line)
96         if match is None and previous_line is not None:
97             # it is possible that we need to match on the previous line as well, so join
98             # them to make them one line and try and get this matching
99             joined_line = ' '.join([previous_line, line])
100             match = re.search(r"(\w+::)*::(?P<tag>\w+)::\w+\(\s*const (?P<event>\w+)", joined_line)
101         if match is not None:
102             self.context.append((match.group('tag'), self.context_depth, match.group('event')))
103         if '{' in line:
104             self.context_depth += 1
105         if '}' in line:
106             self.context_depth -= 1
107             while len(self.context) and self.context[-1][1] == self.context_depth:
108                 self.context.pop()
109
110     def get_state(self, line):
111         if "boost::statechart::state_machine" in line:
112             tokens = re.search(
113                 r"boost::statechart::state_machine<\s*(\w*),\s*(\w*)\s*>",
114                 line)
115             if tokens is None:
116                 raise Exception("Error: malformed state_machine line: " + line)
117             self.machines[tokens.group(1)] = tokens.group(2)
118             self.context.append((tokens.group(1), self.context_depth, ""))
119             return
120         if "boost::statechart::state" in line:
121             tokens = re.search(
122                 r"boost::statechart::state<\s*(\w*),\s*(\w*)\s*,?\s*(\w*)\s*>",
123                 line)
124             if tokens is None:
125                 raise Exception("Error: malformed state line: " + line)
126             self.states[tokens.group(1)] = tokens.group(2)
127             if tokens.group(2) not in self.state_contents.keys():
128                 self.state_contents[tokens.group(2)] = []
129             self.state_contents[tokens.group(2)].append(tokens.group(1))
130             if tokens.group(3) is not "":
131                 self.machines[tokens.group(1)] = tokens.group(3)
132             self.context.append((tokens.group(1), self.context_depth, ""))
133             return
134
135     def get_event(self, line):
136         if "boost::statechart::transition" in line:
137             for i in re.finditer(r'boost::statechart::transition<\s*([\w:]*)\s*,\s*(\w*)\s*>',
138                                  line):
139                 if i.group(1) not in self.edges.keys():
140                     self.edges[i.group(1)] = []
141                 if len(self.context) is 0:
142                     raise Exception("no context at line: " + line)
143                 self.edges[i.group(1)].append((self.context[-1][0], i.group(2)))
144         i = re.search("return\s+transit<\s*(\w*)\s*>()", line)
145         if i is not None:
146             if len(self.context) is 0:
147                 raise Exception("no context at line: " + line)
148             if self.context[-1][2] is "":
149                 raise Exception("no event in context at line: " + line)
150             if self.context[-1][2] not in self.edges.keys():
151                 self.edges[self.context[-1][2]] = []
152             self.edges[self.context[-1][2]].append((self.context[-1][0], i.group(1)))
153
154     def emit_dot(self):
155         top_level = []
156         for state in self.machines.keys():
157             if state not in self.states.keys():
158                 top_level.append(state)
159         print >> sys.stderr, "Top Level States: ", str(top_level)
160         print """digraph G {"""
161         print '\tsize="7,7"'
162         print """\tcompound=true;"""
163         for i in self.emit_state(top_level[0]):
164             print '\t' + i
165         for i in self.edges.keys():
166             for j in self.emit_event(i):
167                 print j
168         print """}"""
169
170     def emit_state(self, state):
171         if state in self.state_contents.keys():
172             self.clusterlabel[state] = "cluster%s" % (str(self.subgraphnum),)
173             yield "subgraph cluster%s {" % (str(self.subgraphnum),)
174             self.subgraphnum += 1
175             yield """\tlabel = "%s";""" % (state,)
176             yield """\tcolor = "blue";"""
177             for j in self.state_contents[state]:
178                 for i in self.emit_state(j):
179                     yield "\t"+i
180             yield "}"
181         else:
182             found = False
183             for (k, v) in self.machines.items():
184                 if v == state:
185                     yield state+"[shape=Mdiamond];"
186                     found = True
187                     break
188             if not found:
189                 yield state+";"
190
191     def emit_event(self, event):
192         def append(app):
193             retval = "["
194             for i in app:
195                 retval += (i + ",")
196             retval += "]"
197             return retval
198         for (fro, to) in self.edges[event]:
199             appendix = ['label="%s"' % (event,)]
200             if fro in self.machines.keys():
201                 appendix.append("ltail=%s" % (self.clusterlabel[fro],))
202                 while fro in self.machines.keys():
203                     fro = self.machines[fro]
204             if to in self.machines.keys():
205                 appendix.append("lhead=%s" % (self.clusterlabel[to],))
206                 while to in self.machines.keys():
207                     to = self.machines[to]
208             yield("%s -> %s %s;" % (fro, to, append(appendix)))
209
210
211 INPUT_GENERATOR = do_filter(sys.stdin.xreadlines())
212 RENDERER = StateMachineRenderer()
213 RENDERER.read_input(INPUT_GENERATOR)
214 RENDERER.emit_dot()