Source code for moddy.dot_fsm

"""
:mod:`dotFsm` -- Generate a graph of a moddy finite state machine
=======================================================================

.. module:: dotFsm
   :synopsis: Generate a graph of a moddy finite state machine
.. moduleauthor:: Klaus Popp <klauspopp@gmx.de>

"""
import subprocess
import os

from .fsm import is_sub_fsm_specification
from .utils import create_dirs_and_open_output_file


[docs]def gen_fsm_graph(fsm, file_name, keep_gv_file=False): """ Generate a Fsm Graph of an fsm using the GraphViz dot tool :param fsm: an instance of the fsm :param file_name: the relative filename including '.svg' :param keep_gv_file: if True, don't delete graphviz input file """ dot_fsm = DotFsm(fsm) dot_fsm.dot_gen(file_name, keep_gv_file)
def _space(indent): istr = "%" + str(3 * indent) + "s" return istr % "" def map_state_names(name, state): if state == "": s = "INIT" else: s = state if name != "": s = name + "_" + s return s class DotFsm(object): """ Display the an fsm via the DOT language (Graphviz) States are vizualized as nodes Subfsms are drawn in separate subgraphs, an edge is drawn from the main state to the initial state in the subfsm """ def __init__(self, fsm): self._fsm = fsm def fsm_gen(self, level, fsm, name="", is_sub_fsm=False): """ generate the dot language statements for a (sub)fsm return lines,initial_state initial_state is only valid on subFsms """ lines = [] initial_state = None dict_transitions = fsm.get_dict_transitions() # States for state, list_trans in dict_transitions.items(): if state == "": sstr = "%s [style=invisible];" % map_state_names(name, state) if is_sub_fsm: sstr = None elif state == "ANY": sstr = ( '%s [label="from any state" shape=none];' % map_state_names(name, state) ) else: sstr = "%s [label=%s];" % (map_state_names(name, state), state) if sstr is not None: lines.append([level, sstr]) # Transitions for from_state, list_trans in dict_transitions.items(): for trans in list_trans: sub_fsm_cls = is_sub_fsm_specification(trans) if sub_fsm_cls is not None: sub_fsm_name, cls = trans # subfsm specification, instantiate subfsm sub_fsm = sub_fsm_cls(parentFsm=fsm) lines.append( [level, "subgraph cluster_%s { " % (sub_fsm_name)] ) lines.append([level + 1, 'label="%s";' % (sub_fsm_name)]) # draw subfsm sub_lines, sub_initial_state = self.fsm_gen( level + 1, sub_fsm, name=sub_fsm_name, is_sub_fsm=True ) lines += sub_lines lines.append([level, "}"]) # draw edge from main state to subfsm initial state lines.append( [ level, "%s -> %s [color=lightgrey];" % ( map_state_names(name, from_state), sub_initial_state, ), ] ) else: # normal transition event, to_state = trans if is_sub_fsm and event == "INITIAL": initial_state = map_state_names(name, to_state) else: lines.append( [ level, '%s -> %s [label="%s"];' % ( map_state_names(name, from_state), map_state_names(name, to_state), event, ), ] ) return lines, initial_state def dot_gen(self, file_name, keep_gv_file): level = 0 lines = [] lines.append([level, "digraph G {"]) lines.append([level + 1, "rankdir=TB;"]) lines.append( [ level + 1, 'graph [fontname = "helvetica" fontsize=10 ' "fontnodesep=0.1];", ] ) lines.append( [ level + 1, 'node [fontname = "helvetica" fontsize=10 ' "shape=ellipse color=black height=.1];", ] ) lines.append( [ level + 1, 'edge [fontname = "helvetica" color=black fontsize=8 ' "fontcolor=black];", ] ) sub_lines, _ = self.fsm_gen(level + 1, self._fsm) lines += sub_lines # finish lines.append([level, "}"]) # Output the DOT file as filename.dot e.g. test.svg.gv dot_file = "%s.gv" % file_name file_desc = create_dirs_and_open_output_file(dot_file) for line in lines: file_desc.write("%s%s\n" % (_space(line[0]), line[1])) file_desc.close() subprocess.check_call(["dot", "-Tsvg", dot_file, "-o%s" % file_name]) print("Saved FSM graph to %s" % file_name) if not keep_gv_file: os.unlink(dot_file)