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.
  • [get | view] (2007-04-16 19:34:05, 11.4 KB) [[attachment:uml2ascii-2004.py]]
  • [get | view] (2007-04-16 19:34:27, 13.5 KB) [[attachment:uml2ascii-moin16.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.