Skip to content

Commit

Permalink
remove_state can deal with nested states, incoming transitions, compo…
Browse files Browse the repository at this point in the history
…und's initial value and history's memory value.
  • Loading branch information
AlexandreDecan committed Jan 19, 2016
1 parent 98496c0 commit 7b38ad0
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 28 deletions.
5 changes: 2 additions & 3 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,18 @@ The YAML format of a statechart also changed, look carefully at the changelog an
- (Changed) Statechart: ``register_state`` is now ``add_state``.
- (Changed) Statechart: ``register_transition`` is now ``add_transition``.
- (Changed) Statechart: now defines a root state.
- (Changed) Statechart: checks done in ``validate``.
- (Changed) Transition: ``.event`` is a string instead of an ``Event`` instance.
- (Changed) Transition: attributes ``from_state`` and ``to_state`` are renamed into ``source`` and ``target``.
- (Changed) Event: ``__eq__`` takes ``data`` attribute into account.
- (Changed) Event: ``event.foo`` raises an ``AttributeError`` instead of a ``KeyError`` if ``foo`` is not defined.
- (Changed) State: ``StateMixin.name`` is now read-only (use ``Statechart.rename_state``).
- (Changed) State: split ``HistoryState`` into a mixin ``HistoryStateMixin`` and two concrete subclasses,
namely ``ShallowHistoryState`` and ``DeepHistoryState``.
- (Changed) IO: Complete rewrite of ``io.import_from_yaml`` to load states before transitions. This function does not anymore
accept a ``validate_statechart`` parameter. Its ``validate_schema`` parameter is now ``not ignore_schema``.
- (Changed) IO: Complete rewrite of ``io.import_from_yaml`` to load states before transitions. Parameter names have changed.
- (Changed) Module: adapt module hierarchy (no visible API change).
- (Changed) Module: expose module content through ``__all__``.
- (Removed) IO: ``io.export_to_yaml``.
- (Removed) Statechart: ``validate`` method.
- (Removed) Transition: ``transitions`` attribute on ``TransitionStateMixin``, use ``Statechart.transitions_for`` instead.
- (Removed) State: ``CompositeStateMixin.children``, use ``Statechart.children_for`` instead.

Expand Down
52 changes: 30 additions & 22 deletions sismic/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,39 +372,47 @@ def add_state(self, state: StateMixin, parent):

def remove_state(self, name: str):
"""
Remove given state. The state can only be removed if it has no more children and no more
incoming transitions. Outgoing transitions are removed in the process.
Remove given state.
The transitions that involve this state will be removed too.
If the state is the target of an *initial* or *memory* property, their value will be set to None.
If the state has children, they will be removed too.
:param name: name of a state
:raise StatechartError:
"""
state = self.state_for(name)

if len(self.children_for(name)) == 0:
# Transitions?
incoming_transitions = self.transitions_to(name)
all_internal = all([t.internal for t in incoming_transitions])

if not all_internal:
raise StatechartError('Cannot remove {} while it has incoming transitions'.format(state))
# Remove children
for child in self.children_for(state.name):
self.remove_state(child)

# Remove incoming transitions (they are internal ones!)
for transition in incoming_transitions:
# Remove transitions
for transition in list(self.transitions): # Make a copy!
if transition.source == state.name or transition.target == state.name:
self.remove_transition(transition)

# Remove outgoing transitions
for transition in self.transitions_from(name):
self.remove_transition(transition)
# Remove compoundstate's initial and historystate's memory
for other_name, other_state in self._states.items():
if isinstance(other_state, CompoundState):
other_state.initial = None
elif isinstance(other_state, HistoryStateMixin):
other_state.memory = None

# Unregister state
try:
self._children.pop(name)
except KeyError:
pass
# Remove state
try:
self._children.pop(name)
except KeyError:
pass
try:
self._parent.pop(name)
self._states.pop(name)
else:
raise StatechartError('Cannot remove {} while it has nested states'.format(state))
except KeyError:
pass

self._states.pop(name)

if self.root == state.name:
self._root = None

def rename_state(self, old_name: str, new_name: str):
"""
Expand Down
7 changes: 4 additions & 3 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,10 @@ def test_remove_unexisting_state(self):
with self.assertRaises(exceptions.StatechartError):
self.sc.remove_state('unknown')

def test_remove_inappropriate_state(self):
with self.assertRaises(exceptions.StatechartError):
self.sc.remove_state('s2')
def test_remove_root_state(self):
self.sc.remove_state('root')
self.assertEqual(len(self.sc.transitions), 0)
self.assertEqual(len(self.sc.states), 0)

def test_remove_appropriate_state(self):
self.sc.remove_state('active')
Expand Down

0 comments on commit 7b38ad0

Please sign in to comment.