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