diff options
author | Richard Ipsum <richard.ipsum@codethink.co.uk> | 2014-02-24 18:21:33 +0000 |
---|---|---|
committer | Richard Ipsum <richard.ipsum@codethink.co.uk> | 2014-03-21 16:47:28 +0000 |
commit | 1de342b8a4cf13b295805855bfaa341bcd86277e (patch) | |
tree | 2b550a0d60532446dad50ee3ecc703a90bb6d780 /distbuild/sm.py | |
parent | f4b503b036f76c23c4f2cb99ca6596823b323035 (diff) | |
download | morph-1de342b8a4cf13b295805855bfaa341bcd86277e.tar.gz |
Add the distbuild libs
Diffstat (limited to 'distbuild/sm.py')
-rw-r--r-- | distbuild/sm.py | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/distbuild/sm.py b/distbuild/sm.py new file mode 100644 index 00000000..f50515c3 --- /dev/null +++ b/distbuild/sm.py @@ -0,0 +1,139 @@ +# mainloop/sm.py -- state machine abstraction +# +# Copyright 2012 Codethink Limited. +# All rights reserved. + + +import logging +import re + + +classnamepat = re.compile(r"<class '(?P<name>.*)'>") + + +class StateMachine(object): + + '''A state machine abstraction. + + The caller may specify call backs for events coming from specific + event sources. An event source might, for example, be a socket + file descriptor, and the event might be incoming data from the + socket. The callback would then process the data, perhaps by + collecting it into a buffer and parsing out messages from it. + + A callback gets the event source and event as arguments. It returns + the new state, and a list of new events to + + A callback may return or yield new events, which will be handled + eventually. They may or may not be handled in order. + + There can only be one callback for one state, source, and event + class combination. + + States are represented by unique objects, e.g., strings containing + the names of the states. When a machine wants to stop, it sets its + state to None. + + ''' + + def __init__(self, initial_state): + self._transitions = {} + self.state = self._initial_state = initial_state + self.debug_transitions = False + + def setup(self): + '''Set up machine for execution. + + This is called when the machine is added to the main loop. + + ''' + + def _key(self, state, event_source, event_class): + return (state, event_source, event_class) + + def add_transition(self, state, source, event_class, new_state, callback): + '''Add a transition to the state machine. + + When the state machine is in the given state, and an event of + a given type comes from a given source, move the state machine + to the new state and call the callback function. + + ''' + + key = self._key(state, source, event_class) + assert key not in self._transitions, \ + 'Transition %s already registered' % str(key) + self._transitions[key] = (new_state, callback) + + def add_transitions(self, specification): + '''Add many transitions. + + The specification is a list of transitions. + Each transition is a tuple of the arguments given to + ``add_transition``. + + ''' + + for t in specification: + self.add_transition(*t) + + def handle_event(self, event_source, event): + '''Handle a given event. + + Return list of new events to handle. + + ''' + + key = self._key(self.state, event_source, event.__class__) + if key not in self._transitions: + if self.debug_transitions: # pragma: no cover + prefix = '%s: handle_event: ' % self.__class__.__name__ + logging.debug(prefix + 'not relevant for us: %s' % repr(event)) + logging.debug(prefix + 'key: %s', repr(key)) + logging.debug(prefix + 'state: %s', repr(self.state)) + return [] + + new_state, callback = self._transitions[key] + if self.debug_transitions: # pragma: no cover + logging.debug( + '%s: state change %s -> %s callback=%s' % + (self.__class__.__name__, self.state, new_state, + str(callback))) + self.state = new_state + if callback is not None: + ret = callback(event_source, event) + if ret is None: + return [] + else: + return list(ret) + else: + return [] + + def dump_dot(self, filename): # pragma: no cover + '''Write a Graphviz DOT file for the state machine.''' + + with open(filename, 'w') as f: + f.write('digraph %s {\n' % self._classname(self.__class__)) + first = True + for key in self._transitions: + state, src, event_class = key + if first: + f.write('"START" -> "%s" [label=""];\n' % + self._initial_state) + first = False + + new_state, callback = self._transitions[key] + if new_state is None: + new_state = 'END' + f.write('"%s" -> "%s" [label="%s"];\n' % + (state, new_state, self._classname(event_class))) + f.write('}\n') + + def _classname(self, klass): # pragma: no cover + s = str(klass) + m = classnamepat.match(s) + if m: + return m.group('name').split('.')[-1] + else: + return s + |