Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius/tutorial.py
diff options
context:
space:
mode:
Diffstat (limited to 'tutorius/tutorial.py')
-rw-r--r--tutorius/tutorial.py261
1 files changed, 187 insertions, 74 deletions
diff --git a/tutorius/tutorial.py b/tutorius/tutorial.py
index a902201..8970d4f 100644
--- a/tutorius/tutorial.py
+++ b/tutorius/tutorial.py
@@ -7,7 +7,7 @@ class Tutorial(object):
_INIT = "INIT"
_END = "END"
- _INITIAL_TRANSITION_NAME = "Transition1"
+ _INITIAL_TRANSITION_NAME = _INIT + "/transition0"
_AUTOMATIC_TRANSITION_EVENT = "automatic"
def __init__(self, name, state_dict=None):
@@ -15,8 +15,10 @@ class Tutorial(object):
The constructor for the Tutorial. By default, the tutorial contains
only an initial state and an end state.
The initial state doesn't contain any action but it contains
- a single automatic transition between the initial state and the
- end state, named _INITIAL_TRANSITION.
+ a single automatic transition <Tutorial._INITIAL_TRANSITION_NAME>
+ between the initial state <Tutorial._INIT> and the end state
+ <Tutorial._END>.
+
The end state doesn't contain any action nor transition.
If state_dict is provided, a valid initial state and an end state
@@ -27,16 +29,23 @@ class Tutorial(object):
@raise InvalidStateDictionary
"""
self.name = name
+
# We will use an adjacency list representation through the
# usage of state objects because our graph representation
# is really sparse and mostly linear, for a brief
# example of graph programming in python see:
# http://www.python.org/doc/essays/graphs
- self._state_dict = state_dict or \
- {Tutorial._INIT:State(name=Tutorial._INIT),\
- Tutorial._END:State(name=Tutorial._END)}
+ if not state_dict:
+ self._state_dict = \
+ {Tutorial._INIT:State(name=Tutorial._INIT),\
+ Tutorial._END:State(name=Tutorial._END)}
+ self.add_transition(Tutorial._INIT, \
+ (Tutorial._AUTOMATIC_TRANSITION_EVENT, Tutorial._END))
+ else:
+ raise NotImplementedError("Tutorial: Initilization from a dictionary is not supported yet")
+
# Minimally check for the presence of an INIT and an END
# state
if not self._state_dict.has_key(Tutorial._INIT):
@@ -44,8 +53,9 @@ class Tutorial(object):
if not self._state_dict.has_key(Tutorial._END):
raise Exception("No END state found in state_dict")
-
- self.validate()
+
+ # TODO: Validate once validation is working
+ #self.validate()
# Initialize variables for generating unique names
# TODO: We should take the max number from the
@@ -77,10 +87,6 @@ class Tutorial(object):
state = State(name, action_list, transition_list)
- if self._state_dict.has_key(name):
- raise Exception("Name: " + name + " already exists, could not\
- add a new state.")
-
self._state_dict[name] = state
return name
@@ -88,7 +94,7 @@ class Tutorial(object):
def add_action(self, state_name, action):
"""
- Add an action to a specific state. A unique name for this
+ Add an action to a specific state. A name unique throughout the
tutorial is generated to refer precisely to this action
and is returned.
@@ -97,12 +103,19 @@ class Tutorial(object):
@param state_name The name of the state to add an action to
@param action The action to be added
@return unique name for this action
+ @raise NameError if state_name doesn't exist
"""
- return "State/Action"
+ if not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: state <" + state_name +\
+ "> is not defined")
+
+ self._validate_action(action)
+
+ return self._state_dict[state_name].add_action(action)
def add_transition(self, state_name, transition):
"""
- Add a transition to a specific state. A unique name for this
+ Add a transition to a specific state. A name unique throughout the
tutorial is generated to refer precisely to this transition
and is returned. Inserting a duplicate transition will raise
an exception.
@@ -112,35 +125,59 @@ class Tutorial(object):
@param state_name The name of the state to add a transition to
@param transition The transition to be added
@return unique name for this action
+ @raise NameError if state_name doesn't exist
@raise TransitionAlreadyExists
"""
- return "State/Transition"
+ if not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: state <" + state_name +\
+ "> is not defined")
+
+ self._validate_transition(transition)
- def update_action(self, action_name, action):
+ # The unicity of the transition is validated by the state
+ return self._state_dict[state_name].add_transition(transition)
+
+ def update_action(self, action_name, new_action):
"""
- Update the properties of a specific action with a copy of the
- properties of the action passed in.
+ Replace the action with action_name by new_action
The action is validated.
- @param action_name The name of the action to update
- @param action An action with the properties to copy from
- @return action_name if the update was successful, False otherwise
+ @param action_name The name of the action to replace
+ @param new_action The action that will replace the old one
+ @return The replaced action
+ @raise NameError if action_name doesn't exist
"""
- return action_name
+ state_name = action_name[:action_name.find("/")]
+
+ if not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: action <" + action_name +\
+ "> is not defined")
+
+ self._validate_action(new_action)
+
+ return self._state_dict[state_name].update_action(action_name, new_action)
- def update_transition(self, transition_name, transition):
+ def update_transition(self, transition_name, new_transition):
"""
- Update the properties of a specific transition with a copy of the
- properties of the transition passed in.
+ Replace the transition with transition_name by new_transition
The transition is validated.
- @param transition_name The name of the transition to update
- @param transition An transition with the properties to copy from
- @return transition_name if the update was successful, False otherwise
+ @param transition_name The name of the transition to replace
+ @param new_transition The transition that will replace the old one
+ @return The replaced transition
+ @raise NameError if transition_name doesn't exist
"""
- return transition_name
+ state_name = transition_name[:transition_name.find("/")]
+
+ if not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: transition <" + transition_name +\
+ "> is not defined")
+
+ self._validate_transition(new_transition)
+
+ return self._state_dict[state_name].update_transition(transition_name, new_transition)
def delete_action(self, action_name):
"""
@@ -148,8 +185,15 @@ class Tutorial(object):
@param action_name The name of the action to be deleted
@return the action that has been deleted
+ @raise NameError if transition_name doesn't exist
"""
- return None
+ state_name = action_name[:action_name.find("/")]
+
+ if not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: action <" + action_name +\
+ "> is not defined")
+
+ return self._state_dict[state_name].delete_action(action_name)
def delete_transition(self, transition_name):
"""
@@ -157,22 +201,61 @@ class Tutorial(object):
@param transition_name The name of the transition to be deleted
@return the transition that has been deleted
+ @raise NameError if transition_name doesn't exist
"""
- return None
+ state_name = transition_name[:transition_name.find("/")]
+
+ if not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: transition <" + transition_name +\
+ "> is not defined")
+
+ return self._state_dict[state_name].delete_transition(transition_name)
def delete_state(self, state_name):
"""
Delete the state, delete all the actions and transitions
in this state, update the transitions from the state that
- pointed to this one to the next state and remove all the
+ pointed to this one to point to the next state and remove all the
unreachable states recursively.
All but the INIT and END states can be deleted.
@param state_name The name of the state to remove
+ @return The deleted state
@raise StateDeletionError when trying to delete the INIT or the END state
+ @raise NameError if state_name doesn't exist
"""
- pass
+ if state_name == Tutorial._INIT or state_name == Tutorial._END:
+ raise StateDeletionError("<" + state_name + "> cannot be deleted")
+
+
+ if not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: state <" + transition_name +\
+ "> is not defined")
+
+ next_states = set(self.get_following_states_dict(state_name).values())
+ previous_states = set(self.get_previous_states_dict(state_name).values())
+
+ # For now tutorials should be completely linear,
+ # let's make sure they are
+ assert len(next_states) <= 1 and len(previous_states) <= 1
+
+ # Update transitions only if they existed
+ if len(next_states) == 1 and len(previous_states) == 1:
+ next_state = next_states.pop()
+ previous_state = previous_states.pop()
+
+ transitions = previous_state.get_transition_dict()
+ for transition_name, (event, state_to_delete) in \
+ transitions.iteritems():
+ self.update_transition(transition_name, (event, next_state.name))
+
+ # Since we assume tutorials are linear for now, we do not need
+ # to search for unreachable states
+
+ return self._state_dict.pop(state_name)
+
+
def get_action_dict(self, state_name=None):
"""
@@ -184,7 +267,16 @@ class Tutorial(object):
@return A dictionary of actions with action_name as key and action as value for state_name
@raise NameError if state_name doesn't exist
"""
- pass
+ if state_name and not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: state <" + state_name +\
+ "> is not defined")
+ elif state_name:
+ return self._state_dict.get_action_dict()
+ else:
+ action_dict = {}
+ for state in self._state_dict.itervalues():
+ action_dict.update(state.get_action_dict())
+ return action_dict
def get_transition_dict(self, state_name=None):
"""
@@ -196,13 +288,23 @@ class Tutorial(object):
@return A dictionary of transitions with transition_name as key and transition as value for state_name
@raise NameError if state_name doesn't exist
"""
- pass
+ if state_name and not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: state <" + state_name +\
+ "> is not defined")
+ elif state_name:
+ return self._state_dict.get_transition_dict()
+ else:
+ transition_dict = {}
+ for state in self._state_dict.itervalues():
+ transition_dict.update(state.get_transition_dict())
+ return transition_dict
+
def get_state_dict(self):
"""
@return A dictionary of all the states in the tutorial with state_name as key and state as value
"""
- pass
+ return self._state_dict
def get_following_states_dict(self, state_name):
"""
@@ -212,8 +314,16 @@ class Tutorial(object):
@param state_name The name of the state
@raise NameError if state_name doesn't exist
"""
- pass
+ if not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: state <" + state_name +\
+ "> is not defined")
+
+ following_states_dict = {}
+ for (event, next_state) in \
+ self._state_dict[state_name].get_transition_dict().itervalues():
+ following_states_dict[next_state] = self._state_dict[next_state]
+ return following_states_dict
def get_previous_states_dict(self, state_name):
"""
@@ -223,7 +333,27 @@ class Tutorial(object):
@param state_name The name of the state
@raise NameError if state_name doesn't exist
"""
- pass
+ if not self._state_dict.has_key(state_name):
+ raise NameError("Tutorial: state <" + state_name +\
+ "> is not defined")
+
+
+ previous_states_dict = {}
+ for iter_state_name, state in \
+ self._state_dict.iteritems():
+
+ for (event, next_state) in \
+ self._state_dict[iter_state_name].get_transition_dict().itervalues():
+
+ if next_state != state_name:
+ continue
+
+ previous_states_dict[iter_state_name] = state
+ # if we have found one, do not look for other transitions
+ # from this state
+ break
+
+ return previous_states_dict
# Convenience methods for common tutorial manipulations
def add_state_before(self, state_name, action_list=[], event_list=[]):
@@ -242,7 +372,7 @@ class Tutorial(object):
@return unique name for this state
@raise NameError if state_name doesn't exist
"""
- pass
+ raise NotImplementedError
# Callback mecanism to allow automatic change notification when
# the tutorial is modified
@@ -261,7 +391,7 @@ class Tutorial(object):
@raise InvalidCallbackFunction if the callback has less or more than
2 arguments
"""
- pass
+ raise NotImplementedError
def register_action_updated_cb(self, cb):
"""
@@ -278,7 +408,7 @@ class Tutorial(object):
@raise InvalidCallbackFunction if the callback has less or more than
2 arguments
"""
- pass
+ raise NotImplementedError
def register_action_deleted_cb(self, cb):
"""
@@ -295,7 +425,7 @@ class Tutorial(object):
@raise InvalidCallbackFunction if the callback has less or more than
2 arguments
"""
- pass
+ raise NotImplementedError
def register_transition_updated_cb(self, cb):
"""
@@ -313,7 +443,7 @@ class Tutorial(object):
@raise InvalidCallbackFunction if the callback has less or more than
2 arguments
"""
- pass
+ raise NotImplementedError
# Validation to assert precondition
def _validate_action(self, action):
@@ -326,15 +456,6 @@ class Tutorial(object):
"""
pass
- def _validate_action_name(self, action_name):
- """
- Check if action_name exists.
-
- @param action_name The name to check
- @except UnknownName if the name is not present in the Tutorial
- """
- pass
-
def _validate_transition(self, transition):
"""
Validate that a transition conforms to what we expect,
@@ -345,15 +466,6 @@ class Tutorial(object):
"""
pass
- def _validate_transition_name(self, transition_name):
- """
- Check if transition_name exists.
-
- @param transition_name The name to check
- @except UnknownName if the name is not present in the Tutorial
- """
- pass
-
def validate(self):
"""
Validate the state machine for a serie of properties:
@@ -365,7 +477,7 @@ class Tutorial(object):
Throw an exception for the first condition that is not met.
"""
- pass
+ raise NotImplementedError
def _generate_unique_state_name(self):
name = "State" + str(self._state_name_nb)
@@ -376,7 +488,7 @@ class Tutorial(object):
"""
Return a string representation of the tutorial
"""
- return ""
+ return str(self._state_dict)
class State(object):
"""
@@ -388,7 +500,7 @@ class State(object):
inputs, the validation should be done by the containing class.
"""
- def __init__(self, name="", action_list=[], transition_list=[]):
+ def __init__(self, name, action_list=[], transition_list=[]):
"""
Initializes the content of the state, such as loading the actions
that are required and building the correct transitions.
@@ -402,6 +514,10 @@ class State(object):
object.__init__(self)
self.name = name
+
+ # Initialize internal variables for name generation
+ self.action_name_nb = 0
+ self.transition_name_nb = 0
self._actions = {}
for action in action_list:
@@ -411,9 +527,6 @@ class State(object):
for transition in transition_list:
self._transitions[self._generate_unique_transition_name(transition)] = transition
- # Initialize internal variables for name generation
- self.action_name_nb = 0
- self.transition_name_nb = 0
# Action manipulations
def add_action(self, new_action):
@@ -438,7 +551,7 @@ class State(object):
if self._actions.has_key(action_name):
return self._actions.pop(action_name)
else:
- raise NameError("Tutorial.State: action '" + action_name + "' is not defined")
+ raise NameError("Tutorial.State: action <" + action_name + "> is not defined")
def update_action(self, action_name, new_action):
"""
@@ -457,7 +570,7 @@ class State(object):
self._actions[action_name] = new_action
return old_action
else:
- raise NameError("Tutorial.State: action '" + action_name + "' is not defined")
+ raise NameError("Tutorial.State: action <" + action_name + "> is not defined")
def get_action_dict(self):
"""
@@ -508,7 +621,7 @@ class State(object):
self._transitions[transition_name] = new_transition
return old_transition
else:
- raise NameError("Tutorial.State: transition '" + transition_name + "' is not defined")
+ raise NameError("Tutorial.State: transition <" + transition_name + "> is not defined")
def delete_transition(self, transition_name):
"""
@@ -521,7 +634,7 @@ class State(object):
if self._transitions.has_key(transition_name):
return self._transitions.pop(transition_name)
else:
- raise NameError("Tutorial.State: transition '" + transition_name + "' is not defined")
+ raise NameError("Tutorial.State: transition <" + transition_name + "> is not defined")
def get_transition_dict(self):
"""
@@ -547,7 +660,7 @@ class State(object):
#TODO use the action class name to generate a name
# to make it easier to debug and know what we are
# manipulating
- name = "action" + str(self.action_name_nb)
+ name = self.name + "/" + "action" + str(self.action_name_nb)
self.action_name_nb += 1
return name
@@ -563,7 +676,7 @@ class State(object):
#TODO use the event class name from the transition to
# generate a name to make it easier to debug and know
# what we are manipulating
- name = "transition" + str(self.transition_name_nb)
+ name = self.name + "/" + "transition" + str(self.transition_name_nb)
self.transition_name_nb += 1
return name