Skip to content

Instantly share code, notes, and snippets.

@elpaso
Created May 3, 2017 16:52
Show Gist options
  • Save elpaso/f4a33583f40651e00e9f840dc3dbb1c9 to your computer and use it in GitHub Desktop.
Save elpaso/f4a33583f40651e00e9f840dc3dbb1c9 to your computer and use it in GitHub Desktop.
Generate a report of Qt connection in cpp files
#!/usr/bin/env python3
"""
Create a graph of Qt connections
"""
import re
import glob
import sys
import tempfile
import argparse
from graphviz import Digraph
__author__ = 'Alessandro Pasotti'
__date__ = '03/05/2017'
__copyright__ = 'Copyright 2017, Boundlessgeo'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
RE=re.compile(r"connect\s*\(\s*([^\s,]+)\s*,\s*([^\s,]+)\s*,\s*([^\s,]+)\s*,\s*([^\s,]+)\s*\)")
connections = []
class Connection():
def __init__(self, signal, slot, path, line, txt):
self.signal = signal
self.slot = slot
self.path = path
self.line = line
self.txt = txt
def find_connections(path, include, exclude):
result = []
with open(path) as f:
i = 1;
for line in f.readlines():
try:
skip = False
_, signal, _, slot = RE.findall(line)[0]
if include and not re.search(include, line):
skip = True
if exclude and re.search(exclude, line):
skip = True
if not skip:
result.append(Connection(signal, slot, path, i, line))
except Exception as e:
pass
i += 1
return result
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--path", help="The path from where start to search for .cpp files. Defaults to current path", default=".")
parser.add_argument("-v", "--verbosity", help="Increase output verbosity", action="store_true")
parser.add_argument("-e", "--engine", help="Dot engine to use: dot, neato and fdp are supported", default='dot')
parser.add_argument("-x", "--exclude", help="Regexp to exclude certain classes or signals slots (it will be applied to both)")
parser.add_argument("-i", "--include", help="Regexp to include only certain classes or signals slots (it will be applied to both)")
args = parser.parse_args()
start = args.path
if not start.endswith('/'):
start = start + '/'
filter = sys.argv[2] if len(sys.argv) > 2 else None
for path in glob.glob('%s**/*.cpp' % start, recursive=True):
if args.verbosity:
print("Searching %s ... " % path)
connections.extend(find_connections(path, args.include, args.exclude))
if len(connections):
dot = Digraph(comment='Signals and slots', engine=args.engine)
out = tempfile.mktemp('.dot')
signals = {}
slots = {}
edges = []
def _mknode(signal, slot):
signal = signal.replace('&', '').replace('::', '-')
slot = slot.replace('&', '').replace('::', '-')
cls, si = signal.split('-')
if cls not in signals:
signals[cls] = []
if si not in signals[cls]:
signals[cls].append(si)
cls, sl = slot.split('-')
if cls not in slots:
slots[cls] = []
if sl not in slots[cls]:
slots[cls].append(sl)
edges.append((signal, slot))
for c in connections:
try:
n = _mknode(c.signal, c.slot)
except:
print("Error parsing in %s : %s\n%s" % (c.path, c.line, c.txt))
dot.attr('node', shape='rectangle')
for cls, sl in signals.items():
with dot.subgraph(name='cluster_%s' % cls) as c:
c.attr(style='filled')
c.attr(color='lightgrey')
c.node_attr.update(style='filled', color='white')
[c.node("%s-%s" % (cls, s), s) for s in sl]
try:
c.node_attr.update(style='filled', color='yellow')
[c.node("%s-%s" % (cls, s), s) for s in slots[cls]]
del(slots[cls])
except Exception as e:
pass
c.attr(label=cls)
for cls, sl in slots.items():
if cls not in signals:
with dot.subgraph(name='cluster_%s' % cls) as c:
c.attr(style='filled')
c.attr(color='lightgrey')
c.node_attr.update(style='filled', color='white')
[c.node("%s-%s" % (cls, s), s) for s in sl]
c.attr(label=cls)
for e in edges:
dot.edge(*e)
if args.verbosity:
print("Dot stored in: %s" % out)
dot.render(out, view=True)
else:
print("Nothing found!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment