_visualize.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. from __future__ import print_function
  2. import argparse
  3. import sys
  4. import graphviz
  5. from ._discover import findMachines
  6. def _gvquote(s):
  7. return '"{}"'.format(s.replace('"', r'\"'))
  8. def _gvhtml(s):
  9. return '<{}>'.format(s)
  10. def elementMaker(name, *children, **attrs):
  11. """
  12. Construct a string from the HTML element description.
  13. """
  14. formattedAttrs = ' '.join('{}={}'.format(key, _gvquote(str(value)))
  15. for key, value in sorted(attrs.items()))
  16. formattedChildren = ''.join(children)
  17. return u'<{name} {attrs}>{children}</{name}>'.format(
  18. name=name,
  19. attrs=formattedAttrs,
  20. children=formattedChildren)
  21. def tableMaker(inputLabel, outputLabels, port, _E=elementMaker):
  22. """
  23. Construct an HTML table to label a state transition.
  24. """
  25. colspan = {}
  26. if outputLabels:
  27. colspan['colspan'] = str(len(outputLabels))
  28. inputLabelCell = _E("td",
  29. _E("font",
  30. inputLabel,
  31. face="menlo-italic"),
  32. color="purple",
  33. port=port,
  34. **colspan)
  35. pointSize = {"point-size": "9"}
  36. outputLabelCells = [_E("td",
  37. _E("font",
  38. outputLabel,
  39. **pointSize),
  40. color="pink")
  41. for outputLabel in outputLabels]
  42. rows = [_E("tr", inputLabelCell)]
  43. if outputLabels:
  44. rows.append(_E("tr", *outputLabelCells))
  45. return _E("table", *rows)
  46. def makeDigraph(automaton, inputAsString=repr,
  47. outputAsString=repr,
  48. stateAsString=repr):
  49. """
  50. Produce a L{graphviz.Digraph} object from an automaton.
  51. """
  52. digraph = graphviz.Digraph(graph_attr={'pack': 'true',
  53. 'dpi': '100'},
  54. node_attr={'fontname': 'Menlo'},
  55. edge_attr={'fontname': 'Menlo'})
  56. for state in automaton.states():
  57. if state is automaton.initialState:
  58. stateShape = "bold"
  59. fontName = "Menlo-Bold"
  60. else:
  61. stateShape = ""
  62. fontName = "Menlo"
  63. digraph.node(stateAsString(state),
  64. fontame=fontName,
  65. shape="ellipse",
  66. style=stateShape,
  67. color="blue")
  68. for n, eachTransition in enumerate(automaton.allTransitions()):
  69. inState, inputSymbol, outState, outputSymbols = eachTransition
  70. thisTransition = "t{}".format(n)
  71. inputLabel = inputAsString(inputSymbol)
  72. port = "tableport"
  73. table = tableMaker(inputLabel, [outputAsString(outputSymbol)
  74. for outputSymbol in outputSymbols],
  75. port=port)
  76. digraph.node(thisTransition,
  77. label=_gvhtml(table), margin="0.2", shape="none")
  78. digraph.edge(stateAsString(inState),
  79. '{}:{}:w'.format(thisTransition, port),
  80. arrowhead="none")
  81. digraph.edge('{}:{}:e'.format(thisTransition, port),
  82. stateAsString(outState))
  83. return digraph
  84. def tool(_progname=sys.argv[0],
  85. _argv=sys.argv[1:],
  86. _syspath=sys.path,
  87. _findMachines=findMachines,
  88. _print=print):
  89. """
  90. Entry point for command line utility.
  91. """
  92. DESCRIPTION = """
  93. Visualize automat.MethodicalMachines as graphviz graphs.
  94. """
  95. EPILOG = """
  96. You must have the graphviz tool suite installed. Please visit
  97. http://www.graphviz.org for more information.
  98. """
  99. if _syspath[0]:
  100. _syspath.insert(0, '')
  101. argumentParser = argparse.ArgumentParser(
  102. prog=_progname,
  103. description=DESCRIPTION,
  104. epilog=EPILOG)
  105. argumentParser.add_argument('fqpn',
  106. help="A Fully Qualified Path name"
  107. " representing where to find machines.")
  108. argumentParser.add_argument('--quiet', '-q',
  109. help="suppress output",
  110. default=False,
  111. action="store_true")
  112. argumentParser.add_argument('--dot-directory', '-d',
  113. help="Where to write out .dot files.",
  114. default=".automat_visualize")
  115. argumentParser.add_argument('--image-directory', '-i',
  116. help="Where to write out image files.",
  117. default=".automat_visualize")
  118. argumentParser.add_argument('--image-type', '-t',
  119. help="The image format.",
  120. choices=graphviz.FORMATS,
  121. default='png')
  122. argumentParser.add_argument('--view', '-v',
  123. help="View rendered graphs with"
  124. " default image viewer",
  125. default=False,
  126. action="store_true")
  127. args = argumentParser.parse_args(_argv)
  128. explicitlySaveDot = (args.dot_directory
  129. and (not args.image_directory
  130. or args.image_directory != args.dot_directory))
  131. if args.quiet:
  132. def _print(*args):
  133. pass
  134. for fqpn, machine in _findMachines(args.fqpn):
  135. _print(fqpn, '...discovered')
  136. digraph = machine.asDigraph()
  137. if explicitlySaveDot:
  138. digraph.save(filename="{}.dot".format(fqpn),
  139. directory=args.dot_directory)
  140. _print(fqpn, "...wrote dot into", args.dot_directory)
  141. if args.image_directory:
  142. deleteDot = not args.dot_directory or explicitlySaveDot
  143. digraph.format = args.image_type
  144. digraph.render(filename="{}.dot".format(fqpn),
  145. directory=args.image_directory,
  146. view=args.view,
  147. cleanup=deleteDot)
  148. if deleteDot:
  149. msg = "...wrote image into"
  150. else:
  151. msg = "...wrote image and dot into"
  152. _print(fqpn, msg, args.image_directory)