Mini Shell
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)
Zerion Mini Shell 1.0