Commits

Ian George committed b8731d9

Added basic Graphviz output, needs graphviz installed and assumes the "dot" command is on the path

Comments (0)

Files changed (4)

statemachine/fsm.py

     STATE_MACHINE_DEFAULT = "start"
 
 #Some Exceptions
-class FSM_Exception(Exception): 
-    """ 
+class FSM_Exception(Exception):
+    """
     Base exception for the FSM implementation.
     """
     pass
-class FSM_TransitionNotAllowed(FSM_Exception): 
+class FSM_TransitionNotAllowed(FSM_Exception):
     """
     Raised when a state change is prevented, generally when an the attempted change isn't in the exit_states list.
     """
     states.sort(key=lambda x: x[1].creation_counter)
 
     # If this class is subclassing another StateMachine, add that StateMachine's states.
-    # Note that we loop over the bases in *reverse*. This is necessary in
-    # order to preserve the correct order of statess.
     if with_base_states:
-        for base in bases[::-1]:
+        for base in bases:
             if hasattr(base, 'base_states'):
                 states = base.base_states.items() + states
     else:
-        for base in bases[::-1]:
+        for base in bases:
             if hasattr(base, 'declared_states'):
                 states = base.declared_states.items() + states
 
 
 class State(object):
     """
-    Represents each individual state of the machine. 
+    Represents each individual state of the machine.
 
-    exit_states: 
+    exit_states:
         a list of strings representing allowed transitions from this state
 
-    entry_action(exited_state, model): 
-        a function to run on entry into the state. 
+    entry_action(exited_state, model):
+        a function to run on entry into the state.
 
-    exit action(target_state, model): 
+    exit action(target_state, model):
         a function to run on exit from the state, must return
         FSM_TransitionNotAllowed if conditions for the transition are not
-        met. 
+        met.
 
     """
     name = ""
         self.exit_action = exit_action
         for k, v in kwargs.items():
             setattr(self, k, v)
-            
+
+        self.creation_counter = State.creation_counter
+        State.creation_counter += 1
 
     def __unicode__(self):
         if self.label:
         return self.name
 
     def exit(self, target_state, *args, **kwargs):
-        """ 
-        Checks states and exits if possible, if not possible it raises FSM_TransitionNotAllowed 
+        """
+        Checks states and exits if possible, if not possible it raises FSM_TransitionNotAllowed
         """
         if self.exit_action:
             self.exit_action(target_state, *args, **kwargs)
         """
         if self.entry_action:
             return self.entry_action(exited_state, *args, **kwargs)
-        
-class FSM(object):
+
+class BaseFSM(object):
     """
     Simple FSM implementation. Takes a declarative approach to
     defining states, and their interactions and capabilities.
 
-    
+
     """
-    __metaclass__ = DeclarativeStatesMetaclass
-
     def __init__(self, verify_on_execute=True):
         self.states = deepcopy(self.base_states)
         try:
 
         for k, s in self.states.items():
             if '*' in s.exit_states:
-                s.exit_states = [key for key in self.states.keys()]
+                s.exit_states = [key for key in self.states.keys() if key != k]
 
     def __unicode__(self):
         return self.__state
         Returns a list containing the available exit states from the current state
         """
         return [self.states[s] for s in self.state.exit_states]
-        
+
     def verify(self):
         """
         Check that all the states named in exit_states exist.
         self.__state = new_state
 
         return self.state
+
+class FSM(BaseFSM):
+    __metaclass__ = DeclarativeStatesMetaclass

statemachine/management/__init__.py

Empty file added.

statemachine/management/commands/__init__.py

Empty file added.

statemachine/management/commands/stategraph.py

+import sys
+import subprocess
+
+from optparse import make_option
+from django.core.management.base import BaseCommand, CommandError
+
+class Command(BaseCommand):
+    args = '<state class path>'
+    help = 'Generates a png named the same as the class path in the root of the project'
+
+    def handle(self, args, **options):
+        (module, cls) = args.rsplit('.', 1)
+        tmp = __import__(module, fromlist=[cls])
+        stateclass = getattr(tmp, cls)
+        graph_states = stateclass()
+
+        dot_gen = "digraph S {\n"
+        state_list = graph_states.states.items()
+        for name, state in state_list:
+            for exits_to in state.exit_states:
+                name = getattr(state, "label", name)
+                exit_name = getattr(graph_states.states[exits_to], "label", exits_to)
+                dot_gen += '\t"%s" -> "%s";\n' % (name, exit_name)
+
+        dot_gen += "}\n"
+
+        print dot_gen
+
+        proc = subprocess.Popen(["dot", "-Tpng", "-o", "%s.png" % cls],
+                                stdin=subprocess.PIPE)
+        proc.communicate(dot_gen)