RAHHH
This commit is contained in:
230
backend/lib/python3.12/site-packages/automat/_visualize.py
Normal file
230
backend/lib/python3.12/site-packages/automat/_visualize.py
Normal file
@@ -0,0 +1,230 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from functools import wraps
|
||||
from typing import Callable, Iterator
|
||||
|
||||
import graphviz
|
||||
|
||||
from ._core import Automaton, Input, Output, State
|
||||
from ._discover import findMachines
|
||||
from ._methodical import MethodicalMachine
|
||||
from ._typed import TypeMachine, InputProtocol, Core
|
||||
|
||||
|
||||
def _gvquote(s: str) -> str:
|
||||
return '"{}"'.format(s.replace('"', r"\""))
|
||||
|
||||
|
||||
def _gvhtml(s: str) -> str:
|
||||
return "<{}>".format(s)
|
||||
|
||||
|
||||
def elementMaker(name: str, *children: str, **attrs: str) -> str:
|
||||
"""
|
||||
Construct a string from the HTML element description.
|
||||
"""
|
||||
formattedAttrs = " ".join(
|
||||
"{}={}".format(key, _gvquote(str(value)))
|
||||
for key, value in sorted(attrs.items())
|
||||
)
|
||||
formattedChildren = "".join(children)
|
||||
return "<{name} {attrs}>{children}</{name}>".format(
|
||||
name=name, attrs=formattedAttrs, children=formattedChildren
|
||||
)
|
||||
|
||||
|
||||
def tableMaker(
|
||||
inputLabel: str,
|
||||
outputLabels: list[str],
|
||||
port: str,
|
||||
_E: Callable[..., str] = elementMaker,
|
||||
) -> str:
|
||||
"""
|
||||
Construct an HTML table to label a state transition.
|
||||
"""
|
||||
colspan = {}
|
||||
if outputLabels:
|
||||
colspan["colspan"] = str(len(outputLabels))
|
||||
|
||||
inputLabelCell = _E(
|
||||
"td",
|
||||
_E("font", inputLabel, face="menlo-italic"),
|
||||
color="purple",
|
||||
port=port,
|
||||
**colspan,
|
||||
)
|
||||
|
||||
pointSize = {"point-size": "9"}
|
||||
outputLabelCells = [
|
||||
_E("td", _E("font", outputLabel, **pointSize), color="pink")
|
||||
for outputLabel in outputLabels
|
||||
]
|
||||
|
||||
rows = [_E("tr", inputLabelCell)]
|
||||
|
||||
if outputLabels:
|
||||
rows.append(_E("tr", *outputLabelCells))
|
||||
|
||||
return _E("table", *rows)
|
||||
|
||||
|
||||
def escapify(x: Callable[[State], str]) -> Callable[[State], str]:
|
||||
@wraps(x)
|
||||
def impl(t: State) -> str:
|
||||
return x(t).replace("<", "<").replace(">", ">")
|
||||
|
||||
return impl
|
||||
|
||||
|
||||
def makeDigraph(
|
||||
automaton: Automaton[State, Input, Output],
|
||||
inputAsString: Callable[[Input], str] = repr,
|
||||
outputAsString: Callable[[Output], str] = repr,
|
||||
stateAsString: Callable[[State], str] = repr,
|
||||
) -> graphviz.Digraph:
|
||||
"""
|
||||
Produce a L{graphviz.Digraph} object from an automaton.
|
||||
"""
|
||||
|
||||
inputAsString = escapify(inputAsString)
|
||||
outputAsString = escapify(outputAsString)
|
||||
stateAsString = escapify(stateAsString)
|
||||
|
||||
digraph = graphviz.Digraph(
|
||||
graph_attr={"pack": "true", "dpi": "100"},
|
||||
node_attr={"fontname": "Menlo"},
|
||||
edge_attr={"fontname": "Menlo"},
|
||||
)
|
||||
|
||||
for state in automaton.states():
|
||||
if state is automaton.initialState:
|
||||
stateShape = "bold"
|
||||
fontName = "Menlo-Bold"
|
||||
else:
|
||||
stateShape = ""
|
||||
fontName = "Menlo"
|
||||
digraph.node(
|
||||
stateAsString(state),
|
||||
fontame=fontName,
|
||||
shape="ellipse",
|
||||
style=stateShape,
|
||||
color="blue",
|
||||
)
|
||||
for n, eachTransition in enumerate(automaton.allTransitions()):
|
||||
inState, inputSymbol, outState, outputSymbols = eachTransition
|
||||
thisTransition = "t{}".format(n)
|
||||
inputLabel = inputAsString(inputSymbol)
|
||||
|
||||
port = "tableport"
|
||||
table = tableMaker(
|
||||
inputLabel,
|
||||
[outputAsString(outputSymbol) for outputSymbol in outputSymbols],
|
||||
port=port,
|
||||
)
|
||||
|
||||
digraph.node(thisTransition, label=_gvhtml(table), margin="0.2", shape="none")
|
||||
|
||||
digraph.edge(
|
||||
stateAsString(inState),
|
||||
"{}:{}:w".format(thisTransition, port),
|
||||
arrowhead="none",
|
||||
)
|
||||
digraph.edge("{}:{}:e".format(thisTransition, port), stateAsString(outState))
|
||||
|
||||
return digraph
|
||||
|
||||
|
||||
def tool(
|
||||
_progname: str = sys.argv[0],
|
||||
_argv: list[str] = sys.argv[1:],
|
||||
_syspath: list[str] = sys.path,
|
||||
_findMachines: Callable[
|
||||
[str],
|
||||
Iterator[tuple[str, MethodicalMachine | TypeMachine[InputProtocol, Core]]],
|
||||
] = findMachines,
|
||||
_print: Callable[..., None] = print,
|
||||
) -> None:
|
||||
"""
|
||||
Entry point for command line utility.
|
||||
"""
|
||||
|
||||
DESCRIPTION = """
|
||||
Visualize automat.MethodicalMachines as graphviz graphs.
|
||||
"""
|
||||
EPILOG = """
|
||||
You must have the graphviz tool suite installed. Please visit
|
||||
http://www.graphviz.org for more information.
|
||||
"""
|
||||
if _syspath[0]:
|
||||
_syspath.insert(0, "")
|
||||
argumentParser = argparse.ArgumentParser(
|
||||
prog=_progname, description=DESCRIPTION, epilog=EPILOG
|
||||
)
|
||||
argumentParser.add_argument(
|
||||
"fqpn",
|
||||
help="A Fully Qualified Path name" " representing where to find machines.",
|
||||
)
|
||||
argumentParser.add_argument(
|
||||
"--quiet", "-q", help="suppress output", default=False, action="store_true"
|
||||
)
|
||||
argumentParser.add_argument(
|
||||
"--dot-directory",
|
||||
"-d",
|
||||
help="Where to write out .dot files.",
|
||||
default=".automat_visualize",
|
||||
)
|
||||
argumentParser.add_argument(
|
||||
"--image-directory",
|
||||
"-i",
|
||||
help="Where to write out image files.",
|
||||
default=".automat_visualize",
|
||||
)
|
||||
argumentParser.add_argument(
|
||||
"--image-type",
|
||||
"-t",
|
||||
help="The image format.",
|
||||
choices=graphviz.FORMATS,
|
||||
default="png",
|
||||
)
|
||||
argumentParser.add_argument(
|
||||
"--view",
|
||||
"-v",
|
||||
help="View rendered graphs with" " default image viewer",
|
||||
default=False,
|
||||
action="store_true",
|
||||
)
|
||||
args = argumentParser.parse_args(_argv)
|
||||
|
||||
explicitlySaveDot = args.dot_directory and (
|
||||
not args.image_directory or args.image_directory != args.dot_directory
|
||||
)
|
||||
if args.quiet:
|
||||
|
||||
def _print(*args):
|
||||
pass
|
||||
|
||||
for fqpn, machine in _findMachines(args.fqpn):
|
||||
_print(fqpn, "...discovered")
|
||||
|
||||
digraph = machine.asDigraph()
|
||||
|
||||
if explicitlySaveDot:
|
||||
digraph.save(filename="{}.dot".format(fqpn), directory=args.dot_directory)
|
||||
_print(fqpn, "...wrote dot into", args.dot_directory)
|
||||
|
||||
if args.image_directory:
|
||||
deleteDot = not args.dot_directory or explicitlySaveDot
|
||||
digraph.format = args.image_type
|
||||
digraph.render(
|
||||
filename="{}.dot".format(fqpn),
|
||||
directory=args.image_directory,
|
||||
view=args.view,
|
||||
cleanup=deleteDot,
|
||||
)
|
||||
if deleteDot:
|
||||
msg = "...wrote image into"
|
||||
else:
|
||||
msg = "...wrote image and dot into"
|
||||
_print(fqpn, msg, args.image_directory)
|
||||
Reference in New Issue
Block a user