1+ import sys
12import pydot
2- from statemachine .factory import StateMachineMetaclass
3+ import importlib
4+
5+ from ..factory import StateMachineMetaclass
6+ from ..statemachine import BaseStateMachine
37
48
59class DotGraphMachine (object ):
@@ -25,7 +29,9 @@ def __init__(self, machine):
2529
2630 def _get_graph (self ):
2731 machine = self .machine
28- sm_class = machine if isinstance (machine , StateMachineMetaclass ) else machine .__class__
32+ sm_class = (
33+ machine if isinstance (machine , StateMachineMetaclass ) else machine .__class__
34+ )
2935 return pydot .Dot (
3036 "list" ,
3137 graph_type = "digraph" ,
@@ -95,17 +101,11 @@ def _state_as_node(self, state):
95101 return node
96102
97103 def _transition_as_edge (self , transition ):
98-
99104 def _get_condition_repr (cond ):
100105 name = getattr (cond .func , "__name__" , cond .func )
101106 return name if cond .expected_value else "!{}" .format (name )
102107
103- cond = ", " .join (
104- [
105- _get_condition_repr (cond )
106- for cond in transition .cond
107- ]
108- )
108+ cond = ", " .join ([_get_condition_repr (cond ) for cond in transition .cond ])
109109 if cond :
110110 cond = "\n [{}]" .format (cond )
111111 return pydot .Edge (
@@ -133,3 +133,52 @@ def get_graph(self):
133133
134134 def __call__ (self ):
135135 return self .get_graph ()
136+
137+
138+ def import_sm (qualname ):
139+ module_name , class_name = qualname .rsplit ("." , 1 )
140+ module = importlib .import_module (module_name )
141+ smclass = getattr (module , class_name , None )
142+ if not smclass or not issubclass (smclass , BaseStateMachine ):
143+ raise ValueError ("{} is not a subclass of StateMachine" .format (class_name ))
144+
145+ return smclass
146+
147+
148+ def write_image (qualname , out ):
149+ """
150+ Given a `qualname`, that is the fully qualified dotted path to a StateMachine
151+ classe, imports the class and generates a dot graph using the `pydot` lib.
152+ Writes the graph representation to the filename 'out' that will
153+ open/create and truncate such file and write on it a representation of
154+ the graph defined by the statemachine, in the format specified by
155+ the extension contained in the out path (out.ext).
156+ """
157+ smclass = import_sm (qualname )
158+
159+ graph = DotGraphMachine (smclass ).get_graph ()
160+ out_extension = out .rsplit ("." , 1 )[1 ]
161+ graph .write (out , format = out_extension )
162+
163+
164+ def main (argv = None ):
165+ import argparse
166+
167+ parser = argparse .ArgumentParser (
168+ usage = "%(prog)s [OPTION] <classpath> <out>" ,
169+ description = "Generate diagrams for StateMachine classes." ,
170+ )
171+ parser .add_argument (
172+ "classpath" , help = "A fully-qualified dotted path to the StateMachine class."
173+ )
174+ parser .add_argument (
175+ "out" ,
176+ help = "File to generate the image using extension as the output format." ,
177+ )
178+
179+ args = parser .parse_args (argv )
180+ write_image (qualname = args .classpath , out = args .out )
181+
182+
183+ if __name__ == "__main__" : # pragma: no cover
184+ sys .exit (main ())
0 commit comments