Modules |
Files |
Inheritance Tree |
Inheritance Graph |
Name Index |
Config
File: Synopsis/Formatter/Dia.py
1| # $Id: Dia.py,v 1.12 2001/07/19 04:03:05 chalky Exp $
2| #
3| # This file is a part of Synopsis.
4| # Copyright (C) 2000, 2001 Stephen Davies
5| #
6| # Synopsis is free software; you can redistribute it and/or modify it
7| # under the terms of the GNU General Public License as published by
8| # the Free Software Foundation; either version 2 of the License, or
9| # (at your option) any later version.
10| #
11| # This program is distributed in the hope that it will be useful,
12| # but WITHOUT ANY WARRANTY; without even the implied warranty of
13| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14| # General Public License for more details.
15| #
16| # You should have received a copy of the GNU General Public License
17| # along with this program; if not, write to the Free Software
18| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19| # 02111-1307, USA.
20| #
21| # $Log: Dia.py,v $
22| # Revision 1.12 2001/07/19 04:03:05 chalky
23| # New .syn file format.
24| #
25| # Revision 1.11 2001/07/17 22:27:28 chalky
26| # Fixed regression in import line
27| #
28| # Revision 1.10 2001/05/25 13:45:49 stefan
29| # fix problem with getopt error reporting
30| #
31| # Revision 1.9 2001/02/13 06:55:23 chalky
32| # Made synopsis -l work again
33| #
34| # Revision 1.8 2001/01/31 06:51:24 stefan
35| # add support for '-v' to all modules; modified toc lookup to use additional url as prefix
36| #
37| # Revision 1.7 2001/01/27 06:26:41 chalky
38| # Added parameter support
39| #
40| # Revision 1.6 2001/01/24 01:38:36 chalky
41| # Added docstrings to all modules
42| #
43| # Revision 1.5 2001/01/22 19:54:41 stefan
44| # better support for help message
45| #
46| # Revision 1.4 2001/01/22 17:06:15 stefan
47| # added copyright notice, and switched on logging
48| #
49|
50| """Generates a .dia file of unpositioned classes and generalizations."""
51| # THIS-IS-A-FORMATTER
52|
53| import sys, getopt, os, os.path, string, re
54| from Synopsis.Core import Type, AST, Util
55|
56| verbose = 0
57|
58| def k2a(keys):
59| "Convert a keys dict to a string of attributes"
60| return string.join(map(lambda item:' %s="%s"'%item, keys.items()), '')
61|
62| def quote(str):
63| "Remove HTML chars from str"
64| str = re.sub('&', '&', str)
65| str = re.sub('<','<', str)
66| str = re.sub('>','>', str)
67| #str = re.sub('<','lt', str)
68| #str = re.sub('>','gt', str)
69| str = re.sub("'", ''', str)
70| str = re.sub('"', '"', str)
71| return str
72|
73| def usage():
74| print """\
75| Dia Formatter Usage:
76| -m hide methods/operations
77| -a hide attributes/variables
78| """
79|
80| class DiaFormatter(AST.Visitor, Type.Visitor):
81| """Outputs a Dia file
82| """
83| def __init__(self, filename):
84| self.__indent = 0
85| self.__istring = " "
86| self.__filename = filename
87| self.__os = None
88| self.__oidcount = 0
89| self.__inherits = [] # list of from,to tuples
90| self.__objects = {} #maps tuple names to object ids
91| def indent(self): self.__os.write(self.__istring * self.__indent)
92| def incr(self): self.__indent = self.__indent + 1
93| def decr(self): self.__indent = self.__indent - 1
94| def write(self, text): self.__os.write(text)
95| def scope(self): return ()
96|
97| def startTag(self, tagname, **keys):
98| "Starts a tag and indents, attributes using keyword arguments"
99| self.indent()
100| self.write("<%s%s>\n"%(tagname,k2a(keys)))
101| self.incr()
102| def startTagDict(self, tagname, attrs):
103| "Starts a tag and indents, attributes using dictionary argument"
104| self.indent()
105| self.write("<%s%s>\n"%(tagname,k2a(attrs)))
106| self.incr()
107| def endTag(self, tagname):
108| "Un-indents and closes tag"
109| self.decr()
110| self.indent()
111| self.write("</%s>\n"%tagname)
112| def soloTag(self, tagname, **keys):
113| "Writes a solo tag with attributes from keyword arguments"
114| self.indent()
115| self.write("<%s%s/>\n"%(tagname,k2a(keys)))
116| def attribute(self, name, type, value, allow_solo=1):
117| "Writes an attribute with given name, type and value"
118| self.startTag('attribute', name=name)
119| if not value and allow_solo:
120| self.soloTag(type)
121| elif type == 'string':
122| self.indent()
123| self.write('<string>#%s#</string>\n'%value)
124| else:
125| self.soloTag(type, val=value)
126| self.endTag('attribute')
127|
128| def output(self, declarations):
129| """Output declarations to file"""
130| self.__os = open(filename, 'w')
131|
132| self.write('<?xml version="1.0"?>\n')
133| self.startTagDict('diagram', {'xmlns:dia':"http://www.lysator.liu.se/~alla/dia/"})
134|
135| self.doDiagramData()
136| self.startTag('layer', name='Background', visible='true')
137| for decl in declarations:
138| decl.accept(self)
139| for inheritance in self.__inherits:
140| self.doInheritance(inheritance)
141| self.endTag('layer')
142| self.endTag('diagram')
143|
144| self.__os.close()
145|
146| def doDiagramData(self):
147| "Write the stock diagramdata stuff"
148| self.startTag('diagramdata')
149| self.attribute('background', 'color', '#ffffff')
150| self.startTag('attribute', name='paper')
151| self.startTag('composite', type='paper')
152| self.attribute('name','string','A4')
153| self.attribute('tmargin','real','2.82')
154| self.attribute('bmargin','real','2.82')
155| self.attribute('lmargin','real','2.82')
156| self.attribute('rmargin','real','2.82')
157| self.attribute('is_portrait','boolean','true')
158| self.attribute('scaling','real','1')
159| self.attribute('fitto','boolean','false')
160| self.endTag('composite')
161| self.endTag('attribute')
162| self.startTag('attribute', name='grid')
163| self.startTag('composite', type='grid')
164| self.attribute('width_x', 'real', '1')
165| self.attribute('width_y', 'real', '1')
166| self.attribute('visible_x', 'int', '1')
167| self.attribute('visible_y', 'int', '1')
168| self.endTag('composite')
169| self.endTag('attribute')
170| self.startTag('attribute', name='guides')
171| self.startTag('composite', type='guides')
172| self.soloTag('attribute', name='hguides')
173| self.soloTag('attribute', name='vguides')
174| self.endTag('composite')
175| self.endTag('attribute')
176| self.endTag('diagramdata')
177|
178| def doInheritance(self, inherit):
179| "Create a generalization object for one inheritance"
180| from_id, to_id = map(self.getObjectID, inherit)
181| id = self.createObjectID(None)
182| self.startTag('object', type='UML - Generalization', version='0', id=id)
183| self.attribute('obj_pos', 'point', '1,0')
184| self.attribute('obj_bb', 'rectangle', '0,0;2,2')
185| self.startTag('attribute', name='orth_points')
186| self.soloTag('point', val='1,0')
187| self.soloTag('point', val='1,1')
188| self.soloTag('point', val='0,1')
189| self.soloTag('point', val='0,2')
190| self.endTag('attribute')
191| self.startTag('attribute', name='orth_orient')
192| self.soloTag('enum', val='1')
193| self.soloTag('enum', val='0')
194| self.soloTag('enum', val='1')
195| self.endTag('attribute')
196| self.attribute('name', 'string', None)
197| self.attribute('stereotype', 'string', None)
198| self.startTag('connections')
199| self.soloTag('connection', handle='0', to=from_id, connection='6')
200| self.soloTag('connection', handle='1', to=to_id, connection='1')
201| self.endTag('connections')
202| self.endTag('object')
203|
204|
205| def createObjectID(self, decl):
206| "Creates a new object identifier, and remembers it with the given declaration"
207| idstr = 'O'+str(self.__oidcount)
208| if decl: self.__objects[decl.name()] = idstr
209| self.__oidcount = self.__oidcount+1
210| return idstr
211|
212| def getObjectID(self, decl):
213| "Returns the stored identifier for the given object"
214| try:
215| return self.__objects[decl.name()]
216| except KeyError:
217| print "Warning: no ID for",decl.name()
218| return 0
219|
220| def formatType(self, type):
221| "Returns a string representation for the given type"
222| if type is None: return '(unknown)'
223| type.accept(self)
224| return self.__type
225|
226| #################### Type Visitor ###########################################
227|
228| def visitBaseType(self, type):
229| self.__type = Util.ccolonName(type.name())
230|
231| def visitUnknown(self, type):
232| self.__type = Util.ccolonName(type.name(), self.scope())
233|
234| def visitDeclared(self, type):
235| self.__type = Util.ccolonName(type.name(), self.scope())
236|
237| def visitModifier(self, type):
238| aliasStr = self.formatType(type.alias())
239| premod = map(lambda x:x+" ", type.premod())
240| self.__type = "%s%s%s"%(string.join(premod,''), aliasStr, string.join(type.postmod(),''))
241|
242| def visitParametrized(self, type):
243| temp = self.formatType(type.template())
244| params = map(self.formatType, type.parameters())
245| self.__type = "%s<%s>"%(temp,string.join(params, ", "))
246|
247| def visitFunctionType(self, type):
248| ret = self.formatType(type.returnType())
249| params = map(self.formatType, type.parameters())
250| self.__type = "%s(%s)(%s)"%(ret,string.join(type.premod(),''),string.join(params,", "))
251|
252| ################# AST visitor #################
253|
254| def visitDeclaration(self, decl):
255| "Default is to not do anything with it"
256| #print "Not writing",decl.type(), decl.name()
257| pass
258|
259| def visitModule(self, decl):
260| "Just traverse child declarations"
261| # TODO: make a Package UML object and maybe link classes to it?
262| for d in decl.declarations():
263| d.accept(self)
264|
265| def visitClass(self, decl):
266| "Creates a Class object for one class, with attributes and operations"
267| id = self.createObjectID(decl)
268| self.startTag('object', type='UML - Class', version='0', id=id)
269| self.attribute('objpos', 'point', '1,1')
270| self.attribute('obj_bb', 'rectangle', '1,1;2,2')
271| self.attribute('elem_corner', 'point', '1.05,1.05')
272| self.attribute('elem_width', 'real', '1')
273| self.attribute('elem_height', 'real', '5')
274| self.attribute('name', 'string', Util.ccolonName(decl.name()))
275| self.attribute('stereotype', 'string', None)
276| self.attribute('abstract', 'boolean', 'false')
277| self.attribute('suppress_attributes', 'boolean', 'false')
278| self.attribute('suppress_operations', 'boolean', 'false')
279| self.attribute('visible_attributes', 'boolean', hide_attributes and 'false' or 'true')
280| self.attribute('visible_operations', 'boolean', hide_operations and 'false' or 'true')
281| # Do attributes
282| afilt = lambda d: d.type() == 'attribute' or d.type() == 'variable'
283| attributes = filter(afilt, decl.declarations())
284| if not len(attributes) or hide_attributes:
285| self.soloTag('attribute', name='attributes')
286| else:
287| self.startTag('attribute', name='attributes')
288| for attr in attributes:
289| self.startTag('composite', type='umlattribute')
290| self.attribute('name', 'string', attr.name()[-1])
291| if attr.type() == 'attribute':
292| self.attribute('type', 'string', self.formatType(attr.returnType()))
293| else:
294| self.attribute('type', 'string', self.formatType(attr.vtype()))
295| self.attribute('value', 'string', None)
296| self.attribute('visibility', 'enum', '0')
297| self.attribute('abstract', 'boolean', 'false')
298| self.attribute('class_scope', 'boolean', 'false')
299| self.endTag('composite')
300| self.endTag('attribute')
301| # Do operations
302| operations = filter(lambda d: d.type() == 'operation', decl.declarations())
303| if not len(operations) or hide_operations:
304| self.soloTag('attribute', name='operations')
305| else:
306| self.startTag('attribute', name='operations')
307| for oper in operations:
308| self.startTag('composite', type='umloperation')
309| self.attribute('name', 'string', oper.name()[-1])
310| self.attribute('type', 'string', self.formatType(oper.returnType()))
311| self.attribute('visibility', 'enum', '0')
312| self.attribute('abstract', 'boolean', 'false')
313| self.attribute('class_scope', 'boolean', 'false')
314| if len(oper.parameters()) and not hide_params:
315| self.startTag('attribute', name='parameters')
316| for param in oper.parameters():
317| self.startTag('composite', name='umlparameter')
318| self.attribute('name', 'string', param.identifier())
319| self.attribute('type', 'string', '', 0)
320| self.attribute('value', 'string', quote(param.value()))
321| self.attribute('kind', 'enum', '0')
322| self.endTag('composite')
323| self.endTag('attribute')
324| else:
325| self.soloTag('attribute', name='parameters')
326|
327| self.endTag('composite')
328| self.endTag('attribute')
329| # Finish class object
330| self.attribute('template', 'boolean', 'false')
331| self.soloTag('attribute', name='templates')
332| self.endTag('object')
333|
334| for inherit in decl.parents():
335| self.__inherits.append( (inherit.parent(), decl) )
336|
337| def usage():
338| """Print usage to stdout"""
339| print \
340| """
341| -o <file> Output file
342| -m hide operations
343| -a hide attributes
344| -p hide parameters"""
345|
346| def __parseArgs(args):
347| global filename, hide_operations, hide_attributes, hide_params, verbose
348| filename = None
349| hide_operations = hide_attributes = hide_params = 0
350| try:
351| opts,remainder = getopt.getopt(args, "o:mapv")
352| except getopt.error, e:
353| sys.stderr.write("Error in arguments: " + str(e) + "\n")
354| sys.exit(1)
355|
356| for opt in opts:
357| o,a = opt
358|
359| if o == "-o":
360| filename = a
361| elif o == "-m": hide_operations = 1
362| elif o == "-a": hide_attributes = 1
363| elif o == "-p": hide_params = 1
364| elif o == "-v": verbose = 1
365|
366| if filename is None:
367| sys.stderr.write("Error: No output specified.\n")
368| sys.exit(1)
369|
370| def format(args, ast, config_obj):
371| __parseArgs(args)
372|
373| formatter = DiaFormatter(filename)
374|
375| formatter.output(ast.declarations())
376|