Skip to content

Commit

Permalink
Export to plantuml reworked
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexandreDecan committed Feb 26, 2018
1 parent fb7d26c commit fd65703
Showing 1 changed file with 139 additions and 125 deletions.
264 changes: 139 additions & 125 deletions sismic/io/plantuml.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,26 @@
class _Exporter:
def __init__(
self,
statechart: Statechart,
statechart: Statechart, *,
statechart_name=True,
statechart_description=True,
statechart_preamble=True,
state_contracts=True,
state_actions=True,
transition_action=True,
):
transition_contracts=True,
transition_action=True):
self.statechart = statechart
self.statechart_name = statechart_name
self.statechart_description = statechart_description
self.statechart_preamble = statechart_preamble
self.state_contracts = state_contracts
self.state_actions = state_actions
self.transition_contracts = transition_contracts
self.transition_action = transition_action

self._states_id = {}
self._output = []
self._indent = 0
self._note_nb = 0

def indent(self):
self._indent += 2
Expand All @@ -40,6 +41,9 @@ def concat(self, text, *, wrap=''):
lines = text.strip().split('\n')

for line in lines:
# Special case for __old__
line = line.replace('__old__', '~__old__')

self._output.append(
'{indent}{wrap}{line}{wrap}'.format(
indent=' ' * self._indent,
Expand All @@ -49,144 +53,152 @@ def concat(self, text, *, wrap=''):
)

def state_id(self, name):
return name.replace(' ', '')
return ''.join(filter(str.isalnum, name))

def export_statechart(self):
if self.statechart_name or self.statechart_description or self.statechart_preamble:
self.concat('note top of {}'.format(self.state_id(self.statechart.root)))
if self.statechart_name and self.statechart.name:
self.concat('title {}'.format(self.statechart.name))

self.indent()
if self.statechart_name and self.statechart.name:
self.concat(self.statechart.name, wrap='**')
if self.statechart_description and self.statechart.description:
self.concat(self.statechart.description, wrap='//')
if self.statechart_preamble and self.statechart.preamble:
self.concat(self.statechart.preamble)
if self.statechart_description and self.statechart.description:
self.concat('caption {}'.format(self.statechart.description.replace('\n', '\\n')))

if self.statechart_preamble and self.statechart.preamble:
self.concat('note top of {}'.format(self.state_id(self.statechart.root)))
self.indent()
self.concat(self.statechart.preamble)
self.deindent()

self.concat('end note')

def export_state(self, name: str):
state = self.statechart.state_for(name)

self.export_state_actions(name)
self.export_state_contracts(name)
# Special case for final state
if isinstance(state, FinalState):
# Find transitions leading to it
for transition in self.statechart.transitions_to(state.name):
self.export_transition(transition)
return

if isinstance(state, BasicState):
self.concat('state "{}" as {}'.format(name, self.state_id(name)))
elif isinstance(state, CompoundState):
if isinstance(state, ShallowHistoryState):
self.concat('state "H" as {} {{'.format(self.state_id(name)))
elif isinstance(state, DeepHistoryState):
self.concat('state "H*" as {} {{'.format(self.state_id(name)))
else:
self.concat('state "{}" as {} {{'.format(name, self.state_id(name)))
self.indent()
for child in self.statechart.children_for(name):
self.export_state(child)

if state.initial:
self.concat('[*] --> {}'.format(self.state_id(state.initial)))
self.indent()

self.deindent()
self.concat('}')
elif isinstance(state, OrthogonalState):
self.concat('state "{}" as {} {{'.format(name, self.state_id(name)))
self.indent()
# Actions
has_actions = False

for i, child in enumerate(self.statechart.children_for(name)):
if i != 0:
self.concat('--')
self.export_state(child)
if self.state_actions and isinstance(state, ActionStateMixin):
# On entry, on exit
if state.on_entry:
has_actions = True
self.concat('{} : **entry**:\\n{}'.format(
self.state_id(name),
state.on_entry.strip().replace('\n', '\\n')
))
if state.on_exit:
has_actions = True
self.concat('{} : **exit**:\\n{}'.format(
self.state_id(name),
state.on_exit.strip().replace('\n', '\\n')
))

self.deindent()
self.concat('}')
# Internal actions
transitions = [tr for tr in self.statechart.transitions_from(name) if tr.internal and tr.action]
if len(transitions) > 0:
has_actions = True
for transition in transitions:
text = []
if transition.event:
text.append('**{}**'.format(transition.event))
if transition.guard:
text.append('[{}]'.format(transition.guard))

self.concat('{} : {}:\\n{}'.format(
self.state_id(name),
''.join(text),
transition.action.strip().replace('\n', '\\n'),
))

# Contracts
if isinstance(state, ContractMixin) and self.state_contracts and (
state.preconditions or state.invariants or state.postconditions):
if has_actions:
self.concat('{} : '.format(self.state_id(name)))

elif isinstance(state, FinalState):
pass
elif isinstance(state, ShallowHistoryState):
self.concat('state "H - {}" as {}'.format(name, self.state_id(name)))
elif isinstance(state, DeepHistoryState):
self.concat('state "H* - {}" as {}'.format(name, self.state_id(name)))
else:
assert False, type(state)
for cond in state.preconditions:
self.concat('{} : **pre:** {}'.format(self.state_id(name), cond))
for cond in state.invariants:
self.concat('{} : **inv:** {}'.format(self.state_id(name), cond))
for cond in state.postconditions:
self.concat('{} : **post:** {}'.format(self.state_id(name), cond))

def export_state_actions(self, name):
state = self.statechart.state_for(name)
# Nested states
for i, child in enumerate(self.statechart.children_for(name)):
if i != 0 and isinstance(state, OrthogonalState):
self.concat('--')
self.export_state(child)

if not self.state_actions or not isinstance(state, ActionStateMixin):
return
# Initial state
if hasattr(state, 'initial') and state.initial:
self.concat('[*] --> {}'.format(self.state_id(state.initial)))

# On entry, on exit
if state.on_entry:
self.concat('{} : **on entry**:\\n{}'.format(
self.state_id(name),
state.on_entry.strip().replace('\n', '\\n')
))
if state.on_exit:
self.concat('{} : **on exit**:\\n{}'.format(
self.state_id(name),
state.on_exit.strip().replace('\n', '\\n')
))

# Internal actions
transitions = [tr for tr in self.statechart.transitions_from(name) if tr.internal and tr.action]
if len(transitions) > 0:
for transition in transitions:
text = []
if transition.event:
text.append('on event {}'.format(transition.event))
if transition.guard:
text.append('[{}]'.format(transition.guard))

self.concat('{} : **{}**:\\n{}'.format(
self.state_id(name),
''.join(text),
transition.action.strip().replace('\n', '\\n'),
))
self.deindent()
self.concat('}')

def export_transition(self, source_name):
def export_transitions(self, source_name):
state = self.statechart.state_for(source_name)
transitions = self.statechart.transitions_from(source_name)

# History state
if isinstance(state, HistoryStateMixin) and state.memory:
self.concat('{} --> {}'.format(self.state_id(source_name), self.state_id(state.memory)))

# Transitions (except internal ones)
transitions = filter(lambda t: not t.internal, self.statechart.transitions_from(source_name))

for transition in transitions:
if transition.internal:
# Do not treat final states here
if transition.target and isinstance(self.statechart.state_for(transition.target), FinalState):
continue

target = self.statechart.state_for(transition.target)
if isinstance(target, FinalState):
target_name = '[*]'
else:
target_name = self.state_id(target.name)

text = []
if transition.event:
text.append(transition.event)
if transition.guard:
text.append('[{}]'.format(transition.guard))
if transition.action and self.transition_action:
text.append(' / {}'.format(transition.action.replace('\n', '; ')))

self.concat('{source} --> {target} : {text}'.format(
source=self.state_id(source_name),
target=target_name,
text=''.join(text),
))

def export_state_contracts(self, name):
state = self.statechart.state_for(name)
self.export_transition(transition)

if isinstance(state, ContractMixin) and self.state_contracts and (
state.preconditions or state.invariants or state.postconditions):
#self._note_nb += 1
self.concat('note bottom of {}'.format(self.state_id(name)))
# self.concat('note as N{}'.format(self._note_nb))
def export_transition(self, transition):
target = self.statechart.state_for(transition.target)

if isinstance(target, FinalState):
target_name = '[*]'
else:
target_name = self.state_id(target.name)

text = []
if transition.event:
text.append(transition.event)
if transition.guard:
text.append('[{}]'.format(transition.guard))
if transition.action and self.transition_action:
text.append(' / {}'.format(transition.action.replace('\n', '; ')))

if self.transition_contracts and (
transition.preconditions or transition.invariants or transition.postconditions):
text.append('\\n')

for cond in transition.preconditions:
text.append('pre: {}\\n'.format(cond))
for cond in transition.invariants:
text.append('inv: {}\n'.format(cond))
for cond in transition.postconditions:
text.append('post: {}\n'.format(cond))

self.concat('{source} --> {target} : {text}'.format(
source=self.state_id(transition.source),
target=target_name,
text=''.join(text),
))

self.indent()
for cond in state.preconditions:
self.concat('pre: {}'.format(cond))
for cond in state.invariants:
self.concat('inv: {}'.format(cond))
for cond in state.postconditions:
self.concat('post: {}'.format(cond))
self.deindent()
self.concat('end note')

def export_all(self):
self.concat('@startuml')
Expand All @@ -195,7 +207,7 @@ def export_all(self):
self.export_state(self.statechart.root)

for name in self.statechart.states:
self.export_transition(name)
self.export_transitions(name)

self.concat('@enduml')

Expand All @@ -209,29 +221,31 @@ def export_to_plantuml(
statechart_preamble=True,
state_contracts=True,
state_actions=True,
transition_action=True
):
transition_contracts=True,
transition_action=True) -> str:
"""
Export given statechart to plantUML (see http://plantuml/plantuml).
:param statechart: statechart to export
:param statechart_name: include the name of the statechart
:param statechart_description: include the description of the statechart
:param statechart_preamble: include the preamble of the statechart
:param state_contracts: include a note containing state contracts
:param state_contracts: include state contracts
:param state_actions: include state actions (on entry, on exit and internal transitions)
:param transition_contracts: include transition contracts
:param transition_action: include actions on transition
:return:
:return: textual representation using plantuml
"""

exporter = _Exporter(
statechart,
statechart_name,
statechart_description,
statechart_preamble,
state_contracts,
state_actions,
transition_action
statechart_name=statechart_name,
statechart_description=statechart_description,
statechart_preamble=statechart_preamble,
state_contracts=state_contracts,
state_actions=state_actions,
transition_contracts=transition_contracts,
transition_action=transition_action,
)

return exporter.export_all()

0 comments on commit fd65703

Please sign in to comment.