Attachment 'uml2ascii-moin16.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - Parser to create Simple ASCII UML Diagrams
4
5 @copyright: 1998-2004 Senya Basin (MoinMoin:SB),
6 2007 MoinMoin:ThomasWaldmann
7 @license: GNU GPL, see COPYING for details.
8 """
9
10 import re
11 import logging
12
13 USAGE = """ UML2ASCII Usage:
14 {{{#!uml2ascii [Options]
15 Diagram DDL
16 }}}
17 -----------------------------------------------------------
18 DDL Sample:
19 [Box 1] -> [Box2] -(3,1)- [Box 3]
20 [Box 4|...two liner...] -> --(2,)-- [Box 5.hvc=///]
21
22 Box format: [pos_x, pos_y, text, attr]
23 pos_x - column position of the box (optional, auto-incremented by default)
24 pos_y - row position of the box (optional, auto-incremented with new-line by default)
25 text - text to display in the box. Required. Use "|" for multi-line text. Use "|-|" for divider.
26 attr - Optional attributes:
27 hvc=... : use alternative characters: Horizontal, Vertical and Corner.
28 Box samples: [Box1] [Class Point|-|int getX()|int getY()|void setX(int)|void setY(int)] [1,5,Box at cell 1:5]
29
30 Link format: START_CHAR -* FROM_TO -* END_CHAR
31 START_CHAR - starting character. Required.
32 END_CHAR - ending character. Required.
33 FROM_TO - (id_from, id_to). Optional. From previous to next by default.
34 id_from - Box ID from which the links starts. Optional. Previous box by default.
35 id_to - Box ID to which the link goes. Optional. Next box by default.
36 Link samples: -> <-> -- <> -(2,5)-> -(,7)->
37
38 Options:
39 fg:color - diagram color
40 dx:value - minimum horizontal gap
41 dy:value - minimum vertical gap
42 nn: - print box IDs
43 """
44
45 H_CHAR = u'-'
46 V_CHAR = u'|'
47 C_CHAR = u'+'
48
49 class UMLAsciiDiagram:
50 def __init__(self):
51 self.pos_x = 0
52 self.pos_y = 0
53 self.obj_id = 0
54 self.tab_nx = 0
55 self.tab_ny = 0
56 self.boxes = {}
57 self.links_from = {}
58 self.cols_width = {}
59 self.rows_height = {}
60 self.cols_start = []
61 self.rows_start = []
62 self.DEBUG = 0
63 self.fgcolor = 'black'
64 self.DX = 5
65 self.DY = 3
66 self.NN = 0
67
68 def parse_params(self, args):
69 m = re.match('.*(fgcolor|fg):(\S+).*', args, re.I)
70 if m:
71 self.fgcolor = m.group(2)
72
73 m = re.match('.*DY:(\d+)\s.*', args, re.I)
74 if m:
75 self.DY = int(m.group(1))
76
77 m = re.match('.*DX:(\d+)\s.*', args, re.I)
78 if m:
79 self.DX = int(m.group(1))
80
81 if re.match('.*NN:.*', args, re.I):
82 self.NN = 1
83
84 if re.match('.*DEBUG:.*', args, re.I):
85 self.DEBUG = 1
86
87 def pdebug(self, *args):
88 if self.DEBUG:
89 logging.debug(args)
90
91 def parse_ddl(self, args):
92 re_box = re.compile(r'\[(\d*),?(\d*),?"?(.*?)\s*?"?,?(\w*?=.*?)?\]')
93 re_link = re.compile(r'(\s?[<-])[-(]*(\d*),?(\d*)[-)]*([->])\s?')
94
95 if re.match('^\s*$', args):
96 return 0
97
98 rc = 0
99 self.pos_y += 1
100 self.pos_x = 0
101
102 for tok in re.findall('\[.*?\]|[<-].*?[->]\s', args):
103 #s.pdebug("parsed TOKEN:", tok)
104 if re.match('\[\]', tok):
105 self.pos_x += 1
106 continue
107
108 m = re_box.match(tok)
109 if m:
110 x, y, text, attr = m.group(1, 2, 3, 4)
111 self.add_box(x, y, text, attr)
112 rc = 1
113
114 m = re_link.match(tok)
115 if m:
116 fr, to, start, end = m.group(2, 3, 1, 4)
117 self.add_link(fr, to, start.strip(), end.strip())
118
119 return rc
120
121 def add_box(self, x='', y='', text='', attr='', id=None):
122 self.obj_id = parse_int(id, self.obj_id + 1)
123 self.pos_x = parse_int(x, self.pos_x + 1)
124 self.pos_y = parse_int(y, self.pos_y)
125
126 hvc = ''
127 if attr:
128 m = re.match('hvc=(...)', attr)
129 if m:
130 hvc = m.group(1)
131 m = re.match('id=(\d+)', attr)
132 if m:
133 self.obj_id = m.group(1)
134
135 self.pdebug('BOX: x=', self.pos_x, '; y=', self.pos_y, "; text='",text,"'; hvc=",hvc,"; id=",self.obj_id)
136
137 width, height, lines = make_textbox(text, hvc)
138 self.boxes[self.obj_id] = (self.obj_id, self.pos_x, self.pos_y, text, attr, width, height, lines)
139
140 if not self.cols_width.has_key(self.pos_x) or width > self.cols_width[self.pos_x]:
141 self.cols_width[self.pos_x] = width
142
143 if not self.rows_height.has_key(self.pos_y) or height > self.rows_height[self.pos_y]:
144 self.rows_height[self.pos_y] = height
145
146 def add_link(self, from_id='', to_id='', start=H_CHAR, end=H_CHAR, attr=None):
147 from_id = parse_int(from_id, self.obj_id)
148 to_id = parse_int(to_id, self.obj_id + 1)
149
150 if not self.links_from.has_key(from_id):
151 self.links_from[from_id] = []
152
153 links = self.links_from[from_id]
154 links.append([from_id, to_id, start, end, attr])
155 self.pdebug ("LINK: from=",from_id," to=",to_id," link=\""+start+end+"\" attr=",attr)
156
157 def define_geometry(self):
158 self.tab_nx = len(self.cols_width.keys())
159 self.tab_ny = len(self.rows_height.keys())
160
161 for i in range(self.tab_nx + 1):
162 self.cols_start.append(0)
163
164 for i in range(2, self.tab_nx + 1):
165 self.cols_start[i] = self.cols_start[i-1] + self.cols_width[i-1] + self.DX
166
167 for i in range(self.tab_ny + 1):
168 self.rows_start.append(0)
169
170 for i in range(2, self.tab_ny + 1):
171 self.rows_start[i] = self.rows_start[i-1] + self.rows_height[i-1] + self.DY
172
173 return self.tab_nx * self.tab_ny
174
175 def draw_box(self, screen, box_id):
176 box_id, tx, ty, text, attr, width, height, lines = self.boxes[box_id]
177 for j in range(height):
178 yy = self.rows_start[ty] + j
179 for i in range(width):
180 xx = self.cols_start[tx] + i
181 screen[yy][xx] = lines[j][i]
182
183 if self.NN:
184 sid = repr(box_id)
185 for i in range(len(sid)):
186 screen[self.rows_start[ty]][self.cols_start[tx] + i] = sid[i]
187
188 def draw_hline (self, screen, x1, x2, y, c_start, c_end):
189 step = (x2 >= x1) and 1 or -1
190 for x in range(x1, x2, step):
191 screen[y][x] = H_CHAR
192
193 if c_start:
194 if step == -1:
195 if c_start == u'<':
196 c_start = u'>'
197 start_offs = -1
198 else:
199 start_offs = 0
200 screen[y][x1+start_offs] = c_start
201 if c_end:
202 if step == -1:
203 if c_end == u'>':
204 c_end = u'<'
205 end_offs = 0
206 else:
207 end_offs = -1
208 screen[y][x2+end_offs] = c_end
209
210 def draw_h2line (self, screen, x1, x2, y, c_start, c_end):
211 self.draw_hline(screen, x1, x2+2, y+1, c_start, c_end)
212 screen[y][x1] = V_CHAR
213 screen[y+1][x1] = C_CHAR
214 screen[y][x2+1] = V_CHAR
215 screen[y+1][x2+1] = C_CHAR
216
217 def draw_vline (self, screen, x, y1, y2, c_start=V_CHAR, c_end=V_CHAR):
218 step = (y2 >= y1) and 1 or -1
219 for y in range(y1, y2, step):
220 screen[y][x] = V_CHAR
221
222 if c_start:
223 if step >= 0:
224 if c_start == u'<':
225 c_start = u'^'
226 elif c_start == u'-':
227 c_start = V_CHAR
228 else:
229 if c_start == u'<':
230 c_start = u'v'
231 elif c_start == u'-':
232 c_start = V_CHAR
233 screen[y1][x] = c_start
234 if c_end:
235 if step >= 0:
236 if c_end == u'>':
237 c_end = u'v'
238 elif c_end == u'-':
239 c_end = V_CHAR
240 end_offs = -1
241 else:
242 if c_end == u'>':
243 c_end = u'^'
244 elif c_end == u'-':
245 c_end = V_CHAR
246 end_offs = 0
247 screen[y2+end_offs][x] = c_end
248
249 def draw_link(self, screen, link):
250 fr, to, start, end, attr = link
251
252 box_id1, tx1, ty1, undef, undef, w1, h1, undef = self.boxes[fr]
253 if not box_id1:
254 return
255
256 box_id2, tx2, ty2, undef, undef, w2, h2, undef = self.boxes[to]
257 if not box_id2:
258 return
259
260 if ty1 == ty2:
261 if abs(tx2 - tx1) == 1:
262 if tx1 < tx2:
263 x1 = self.cols_start[tx1] + w1
264 x2 = self.cols_start[tx2]
265 else:
266 x1 = self.cols_start[tx1]
267 x2 = self.cols_start[tx2] + w2
268
269 h = min(h1, h2) // 2
270
271 self.draw_hline(screen, x1, x2, self.rows_start[ty1] + h, start, end)
272
273 elif abs(tx2 - tx1) > 1:
274 if tx1 < tx2:
275 x1 = self.cols_start[tx1] + w1 // 2
276 x2 = self.cols_start[tx2] + w2 // 2 - 1
277 else:
278 x1 = self.cols_start[tx2] + w2 // 2
279 x2 = self.cols_start[tx1] + w1 // 2 - 1
280
281 h = max(h1, h2)
282
283 self.draw_h2line(screen, x1, x2, self.rows_start[ty1] + h, start, end)
284
285 elif tx1 == tx2 and abs(ty2 - ty1) == 1:
286 if ty1 < ty2:
287 y1 = self.rows_start[ty1] + self.rows_height[ty1]
288 y2 = self.rows_start[ty2]
289 else:
290 y1 = self.rows_start[ty2] + self.rows_height[ty2]
291 y2 = self.rows_start[ty1]
292
293 w = min(w1, w2) // 2
294
295 self.draw_vline(screen, self.cols_start[tx1] + w, y1, y2, start, end)
296
297 else:
298 if tx1 < tx2:
299 x1 = self.cols_start[tx1] + w1
300 x2 = self.cols_start[tx2]
301 y1 = self.rows_start[ty1] + h1 // 2
302 y2 = self.rows_start[ty2] + h2 // 2
303 w = self.DX // 2
304 self.draw_hline(screen, x1, x1 + w, y1, start, H_CHAR)
305 self.draw_vline(screen, x1 + w, y1, y2)
306 self.draw_hline(screen, x1 + w, x2, y2, H_CHAR, end)
307 else:
308 x1 = self.cols_start[tx1] + w1 // 2
309 x2 = self.cols_start[tx2] + w2 // 2
310 y1 = self.rows_start[ty1] + h1
311 y2 = self.rows_start[ty2]
312 h = self.DY // 2
313 self.draw_vline(screen, x1, y1, y1 + h, start, V_CHAR)
314 self.draw_hline(screen, x1, x2, y1 + h, H_CHAR, H_CHAR)
315 self.draw_vline(screen, x2, y1 + h, y2, V_CHAR, end)
316
317 def draw_diagram(self):
318 screen_w = self.cols_start[self.tab_nx] + self.cols_width[self.tab_nx]
319 screen_h = self.rows_start[self.tab_ny] + self.rows_height[self.tab_ny]
320 screen = [[u' ' for x in range(screen_w)] for y in range(screen_h)]
321
322 for links in self.links_from.values():
323 for link in links:
324 self.draw_link(screen, link)
325
326 for box_id in self.boxes.keys():
327 self.draw_box(screen, box_id)
328
329 return self.print_screen(screen)
330
331 def print_screen(self, screen):
332 return u'\n'.join([u''.join(line) for line in screen]) + u'\n'
333
334
335 def make_textbox (text, symbols=''):
336 """ split text into lines and render it into a box
337 return total width, total height and formatted lines
338 """
339 if not symbols:
340 symbols = H_CHAR + V_CHAR + C_CHAR
341 hchar, vchar, cchar = symbols
342
343 lines = text.split(V_CHAR)
344 max_len = max([len(line) for line in lines])
345
346 # delimiter and top/bottom horizontal lines
347 hline = u'-' * (max_len + 2)
348 top_bottom_line = u"%s%s%s" % (cchar, hline, cchar)
349
350 # now render box
351 formatted_lines = [top_bottom_line]
352 for line in lines:
353 if re.match('\s*\-+\s*', line): # delimiter line wanted
354 l = hline
355 else: # normal content
356 l = u" %s " % line.ljust(max_len)
357 formatted_lines.append(u"%s%s%s" % (vchar, l, vchar))
358 formatted_lines.append(top_bottom_line)
359
360 return max_len + 4, len(formatted_lines), formatted_lines
361
362
363 def parse_int(val, dflt):
364 if val:
365 return int(val)
366 else:
367 return int(dflt)
368
369
370 Dependencies = ['justfordebugging']
371
372 class Parser:
373 """
374 """
375
376 ## specify extensions willing to handle (for inline:)
377 extensions = ['.uml2ascii', ]
378 Dependencies = ['justfordebugging']
379
380 def __init__(self, raw, request, **kw):
381 self.raw = raw
382 self.request = request
383 self.form = request.form
384 self._ = request.getText
385
386 def format(self, formatter):
387 """ Send the text. """
388 request = self.request
389 lines = self.raw.split('\n')
390 ad = UMLAsciiDiagram()
391 ad.parse_params(lines[0])
392 del lines[0]
393
394 rc = 0
395 for line in lines:
396 rc += ad.parse_ddl(line)
397
398 if rc > 0:
399 ad.define_geometry()
400 sb = ad.draw_diagram()
401 else:
402 sb = '\n'.join(lines)
403 sb = USAGE + sb
404
405 request.write(formatter.preformatted(1))
406 request.write(formatter.rawHTML(sb))
407 request.write(formatter.preformatted(0))
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.