at Fri, 27 Mar 2009 12:01:21 +0100, Hans Breuer wrote:
> > Hi,
> >is there some way to store information in the dia objects during export
> >with a python plugin?
> Depends on what version of Dia you are using. With 0.97-pre there is
> generic object metainfo. Just use:
> obj.properties["meta"] = {'mykey' : 'myvalue' }
> which then gets stored and loaded with Dia's standard load/save.
Thanks. I'll try that. I had 0.96.1. So today I fetched 0.97... from svn.
> Is this generate from PyDia? Maybe you can show some source?
> (Given the recent increase in PyDia questions I wonder why there are
> no user provide PyDia plug-ins. Are you all just developing
> proprietary stuff?)
No, it's not proprietary. And I plan to submit a patch quite soon. The current
(alpha!!!) version (not cleaned up, still using a dictionary instead of
metainfo) is appended.
Sebastian
# State-Chart Code Generation from UML Diagram
# Copyright (c) 2009 Sebastian Setzer
# Use at your own risk. There's no support, no warranty, nothing at all for this prototype version.
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# templates
# header
t_hdr = '''
--- --- start
#ifndef $include_guard
#define $include_guard
class $classname
{
enum State
{
--- --- enum_value
$name = $id,
--- --- between_enum_and_triggers
STATE_COUNT = $state_count
} m_states;
void setState(State state);
void do();
public:
$classname();
--- --- triggerdecl
void $trigger();
--- --- end
}
#endif /* $include_guard */
'''
# compilation unit
t_cpp = '''
--- --- start
#include "$filename.h"
$classname::$classname()
: m_states(START)
{}
void $classname::setState(State state)
{
// exit old state
switch(m_state)
{
--- --- case
case $label:
--- --- action
$action;
--- --- guarded_action
if($guard)
$action;
--- --- case_break
break;
--- --- between_exit_and_enter
}
// enter new state
m_state = state;
switch(m_state)
{
--- --- setstate_end
}
}
--- --- do_start
void $classname::do()
{
switch(m_state)
{
--- --- do_end
}
}
--- --- triggerdef_start
void $classname::$trigger()
{
switch(m_state)
{
--- --- triggerdef_guard_start
if($guard)
{
--- --- triggerdef_guarded_action
$action;
--- --- triggerdef_guard_end
}
--- --- error_multiple_unguarded_transitions
Error: multiple unguarded transitions.
--- --- triggerdef_action
$action;
--- --- triggerdef_end
}
}
--- --- end
// end of $classname
'''
# substitute into a string, not into a file:
t_sub = '''
--- --- triggerdef_setstate -n
setState($state)
'''
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
import sys, dia, re, os.path
from string import Template
import re
sep_re = re.compile('---(.*)\n', re.M)
class Templates:
def __init__(self, template_string):
parts = sep_re.split(template_string)[1:]
self.templates = {}
while parts:
(sep_line, template), parts = parts[:2], parts[2:]
argv=sep_line.strip('- ').split()
key=argv[0]
for arg in argv[1:]:
# no last newline, as in echo -n
if arg=='-n' and template.endswith('\n'): template=template[:-1]
else: raise '%s: ununsed argument "%s"' % (key, arg)
self.templates[key] = Template(template)
def __getattr__(self, name):
return self.templates[name].substitute
class FileTemplates(Templates):
def __init__(self, file, template_string):
Templates.__init__(self, template_string)
self.file=file
def __getattr__(self, name):
return lambda **kws: self.file.write(self.templates[name].substitute(kws))
def transition_label(o):
l = o.properties["trigger"].value
if o.properties["guard"].value:
l += " [%s]" % o.properties["guard"].value
if o.properties["action"].value:
l += " / %s" % o.properties["action"].value
return l
def char2cpp(c):
if not c.isalnum(): return '_'
return c
def make_t_cpp_id(s):
s = ''.join(map(char2cpp, s))
if not s: s = '_'
if s[0].isdigit(): s = '_' + s
return s
# For every edge there is a trigger, an guard and an action attribute.
# For states, there are 3 predefined triggers entry, do, exit.
# Unfortunately, these have only actions, no guards.
# This function checks if the action matches a "[guard] action" - pattern.
# If it does, it extracts the guard.
def extract_guard(action):
action = action.strip()
if action.startswith('['):
level = 0
for i,c in enumerate(action):
if c=='[':
level += 1
elif c==']':
level -= 1
if level == 0: # found matching ']'
return action[1:i].strip(), action[i+1:].strip()
return None, action
class Rectangle:
def __init__(self, o):
self.x1, self.y1, self.x2, self.y2 = tuple(o.properties["obj_bb"].value)
def inside(state,cluster):
s = Rectangle(state)
c = Rectangle(cluster)
return (s.x1 >= c.x1) and (s.x2 <= c.x2) and (s.y1 >= c.y1) and (s.y2 <= c.y2)
# class for dia_dict of dia state objects
def State_reset():
State.dia_dict={}
State.start_ids = 0
State.final_ids = 0
class State:
dia_dict={}
start_ids = 0
final_ids = 0
def __init__(self, o):
self.entry_guard, self.entry_action, self.do_guard, self.do_action, self.exit_guard, self.exit_action = 6*(None,)
if o.type.name == "UML - State":
self.name = o.properties["text"].value.text.strip()
entry, do, exit = o.properties["entry_action"].value, o.properties["do_action"].value, o.properties["exit_action"].value
if entry: self.entry_guard, self.entry_action = extract_guard(entry)
if do: self.do_guard, self.do_action = extract_guard(do)
if exit: self.exit_guard, self.exit_action = extract_guard(exit)
elif o.type.name == "UML - State Term":
if o.properties["is_final"].value:
# final state
self.name = "FINAL_%d" % State.final_ids
State.final_ids += 1
else:
# start state
self.name = "START"
if State.start_ids: name = "START_%d" % State.start_ids
State.start_ids += 1
def dia_dict(o):
return State.dia_dict[o]
def output(filename, start_states, final_states, normal_states, clusters, edges, errors):
filename, ext = os.path.splitext(filename)
path, filename = os.path.split(filename)
classname = filename
filename = filename.lower()
include_guard = '%s_H' % filename.upper()
path = os.path.join(path, filename)
f_hdr = open(path + ".h", "w")
f_cpp = open(path + ".cpp", "w")
hdr = FileTemplates(f_hdr, t_hdr)
cpp = FileTemplates(f_cpp, t_cpp)
sub = Templates(t_sub)
triggers = set()
transition_table = {}
cluster_states = {}
# Output is done in this order, which is probably creation order in dia for the 3 groups of states.
# I think that's quite good for version control of the generated files: states won't get reordered when renamed.
all_states = start_states + normal_states + final_states
# create State objects
for o in all_states:
State.dia_dict[o] = State(o)
# use order of all_states instead of unsorted State.dia_dict.values()
all_gen_info = map(lambda o: dia_dict(o), all_states)
# find states which are geometrically inside clusters
for c in clusters:
cluster_states[c] = filter(lambda s: inside(s,c), all_states)
# TODO:
#
# Edges
# ----------
# An edge with cluser-tgt is equivalent to an edge to the (unique) start state of the cluster
# An edge with cluser-src is equivalent to lots of edges with state-src.
# In Triggers of cluster-edges, "case" could be combined, so the body doesn't need to be duplicated.
# But it would be even more general if we had an "equal body recognition" for switch-es,
# So that could compress non-cluster-switches, too.
# Like this: for each case: d[body].append(case.label) ...
# For this, abstract the different switch-outputs below to a switch-output-function.
#
# Actions
# ------------
# do-actions sind einfach: Hinzufuegen zu den do-actions der enthaltenen states.
# entry- und exit actions: jeder state gehoert zu einer Menge von cluster_states.
# Bei Uebergang wird entry fuer alle cluster_states aufgerufen, die in der Menge
# der cluster_states des alten states nicht drin waren, aber in der des neuen
# states drin sind. exit-action analog.
# Problem: Ein switch in setState ueber alten bzw. neuen state reicht nicht mehr,
# es muss ein switch uexxdiff . ueber die Kombi aus altem+neuem state sein (z.B. verschachtelter switch).
# "wenn neuer state im cluster (erster switch) und alter state nicht im cluster (zweiter switch)
# dann entry action des clusters" ist auch nichts anderes als verschachtelter switch,
# mit dem Nachteil dass mans fuer jeden cluster extra machen muss.
# --> Es darf nicht in setState sondern muss in die Ereignis-Funktionen (die entsprechen den edges)?
# Am besten nicht die actions direkt eingefuegt sondern:
# leaveCluster(C_old1)
# leaveCluster(C_old2)
# setState(s_new)
# enterCluster(C_new1)
# Trotzdem wie oben beschrieben: Cluster-edges vorher in (mehrere) state-edges
# umwandeln, erst dann Menge von leave/enter-clusters bestimmen.
# leave/enter soll naemlich auch f�r edges aufgerufen werden, die an normalen
# states haengen aber die "Grenzen" eines clusters ueberschreiten.
#~ for c in clusters:
#~ print "Cluster", c.properties["text"].value.text
#~ for s in cluster_states[c]:
#~ print " ", dia_dict(s).name
# fill transition_table: dictionary (trigger, src) --> list of (edge-object, src, tgt)
for (e, src, tgt) in edges:
trigger = make_t_cpp_id(e.properties["trigger"].value.strip())
if trigger != "otherwise":
triggers.add(trigger)
# Handle clusters
if src in cluster_states:
src_states = cluster_states[src]
else:
src_states = [src]
if tgt in cluster_states:
# TODO:
# cluster_start = intersection(cluster_states[tgt], start_states)
# assert len(cluster_start) == 1
# tgt = cluster_start[0]
pass
for src_state in src_states:
# append (e, src_state, tgt) to transition_table[(trigger, src_state)]
trans = transition_table.setdefault((trigger, src_state), [])
trans.append((e, src_state, tgt))
transition_table[(trigger, src_state)] = trans
# output hdr
hdr.start(include_guard=include_guard, classname=classname)
for i, s in enumerate(all_gen_info):
hdr.enum_value(name=s.name, id=i)
hdr.between_enum_and_triggers(state_count=len(all_states), classname=classname)
for t in sorted(triggers):
hdr.triggerdecl(trigger=t)
hdr.end(include_guard=include_guard)
# output cpp
cpp.start(filename=filename, classname=classname)
for s in all_gen_info:
cpp.case(label=s.name)
if s.exit_action:
if s.exit_guard:
cpp.guarded_action(guard=s.exit_guard, action=s.exit_action)
else:
cpp.action(action=s.exit_action)
cpp.case_break()
cpp.between_exit_and_enter()
for s in all_gen_info:
cpp.case(label=s.name)
if s.entry_action:
if s.entry_guard:
cpp.guarded_action(guard=s.entry_guard, action=s.entry_action)
else:
cpp.action(action=s.entry_action)
cpp.case_break()
cpp.setstate_end()
cpp.do_start(classname=classname)
for s in all_gen_info:
cpp.case(label=s.name)
if s.do_action:
if s.do_guard:
cpp.guarded_action(guard=s.do_guard, action=s.do_action)
else:
cpp.action(action=s.do_action)
cpp.case_break()
cpp.do_end()
for t in sorted(triggers):
cpp.triggerdef_start(classname=classname, trigger=t)
for o in all_states:
translist = transition_table.setdefault((t, o), [])
if not translist:
translist = transition_table.setdefault(("otherwise", o), [])
if translist:
cpp.case(label=dia_dict(o).name)
unguarded = None
for (edge, src, tgt) in sorted(translist, key=lambda (edge, src, tgt): edge.properties["guard"].value):
# Multiple edges with the same trigger (but different guards)
# must go into the same "case".
if edge.properties["guard"].value:
cpp.triggerdef_guard_start(guard=edge.properties["guard"].value)
if edge.properties["action"].value:
cpp.triggerdef_guarded_action(action=edge.properties["action"].value)
if src != tgt:
cpp.triggerdef_guarded_action(
action=sub.triggerdef_setstate(state=dia_dict(tgt).name))
cpp.triggerdef_guard_end()
elif unguarded:
cpp.error_multiple_unguarded_transitions()
else:
unguarded = (edge, src, tgt)
if unguarded:
(edge, src, tgt) = unguarded
if edge.properties["action"].value:
cpp.triggerdef_action(action=edge.properties["action"].value)
if src != tgt:
cpp.triggerdef_action(
action=sub.triggerdef_setstate(state=dia_dict(tgt).name))
cpp.case_break()
cpp.triggerdef_end()
cpp.end(classname=classname)
f_cpp.close()
f_hdr.close()
def is_uml_state(type):
return (type == "UML - State") or (type == "UML - State Term") or (type == "UML - State Cluster")
class StateRenderer :
"Transforms a diagram to a C++ State-Chart"
def __init__(self) :
pass
def begin_render (self, data, filename) :
normal_states = []
start_states = []
final_states = []
clusters = []
edges = []
errors = []
for layer in data.layers :
# for the moment ignore layer info. But we could use this to spread accross different files
for o in layer.objects :
if o.type.name == "UML - State":
normal_states.append(o)
elif o.type.name == "UML - State Term":
if o.properties["is_final"].value:
final_states.append(o)
else:
start_states.append(o)
elif o.type.name == "UML - Transition" :
t = "Transition %s " % transition_label(o)
c_src = o.handles[0].connected_to
c_tgt = o.handles[1].connected_to
if not (c_src and c_tgt):
errors.append("%s is not connected on both sides" % t)
else:
src = c_src.object
tgt = c_tgt.object
if not is_uml_state(src.type.name):
errors.append("The source of %s is a %s" % (t, src.type.name))
elif not is_uml_state(tgt.type.name):
errors.append("The target of %s is a %s" % (t, tgt.type.name))
else:
edges.append((o, src, tgt))
elif o.type.name == "UML - State Cluster":
clusters.append(o)
#print start_states[0].properties.keys()
output(filename, start_states, final_states, normal_states, clusters, edges, errors)
def end_render(self) :
# do cleanup here if you have set class members
State_reset()
# dia-python keeps a reference to the renderer class and uses it on demand
dia.register_export ("State-Chart Code Generation (C++)", "cpp", StateRenderer())
_______________________________________________
dia-list mailing list
[email protected]
http://mail.gnome.org/mailman/listinfo/dia-list
FAQ at http://live.gnome.org/Dia/Faq
Main page at http://live.gnome.org/Dia