1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - Graphviz Parser 4 Based loosely on GNUPLOT parser by MoinMoin:KwonChanYoung 5 6 @copyright: 2008 Wayne Tucker 7 @license: GNU GPL, see COPYING for details. 8 """ 9 10 """ 11 BASIC USAGE: 12 13 embed a visualization of a graph in a wiki page: 14 15 {{{#!graphviz 16 digraph G { 17 A -> B; 18 }; 19 }}} 20 21 ADVANCED USAGE: 22 23 This parser will check the first lines of the Graphviz data for C++ style 24 comments instructing it to use a different filter (dot, neato, twopi, 25 circo, or fdp - see http://graphviz.org/ for more info), use a different 26 format for the output (see the FORMATS list in the Parser class below), 27 or to generate and pass a client-side image map. 28 29 Options: 30 filter - the filter to use (see Parser.FILTERS) 31 format - the output format (see Parser.FORMATS) 32 cmapx - the map name to use for the client-side image map. Must match 33 the graph name in the graph definition and shouldn't conflict 34 with any other graphs that are used on the same page. 35 36 embed a visualization of a graph in a wiki page, using the dot filter and 37 providing a client-side image map (the filter=dot and format=png options are 38 redundant since those are the defaults for this parser): 39 40 {{{#!graphviz 41 //filter=dot 42 //format=png 43 //cmapx=DocumentationMap 44 digraph DocumentationMap { 45 FrontPage [href="FrontPage", root=true]; 46 HelpOnEditing [href="HelpOnEditing"]; 47 SyntaxReference [href="SyntaxReference"]; 48 WikiSandBox [href="WikiSandBox", color="grey"]; 49 MoinMoin [href="http://moinmo.in"]; 50 FrontPage -> WikiSandBox; 51 FrontPage -> MoinMoin; 52 WikiSandBox -> HelpOnEditing; 53 WikiSandBox -> SyntaxReference; 54 SyntaxReference -> FrontPage; 55 }; 56 }}} 57 58 59 KNOWN BUGS: 60 - Hasn't been thoroughly checked for potential methods of injecting 61 arbitrary HTML into the output. 62 - Only compatible with HTML rendering 63 - May not use all of the MoinMoin interfaces properly - this is a 64 quick hack based on looking at an example and digging through the 65 MoinMoin source. The MoinMoin development docs haven't been 66 consulted (yet). 67 - Only image formats (png and gif) are currently implemented 68 - Comments must start at the beginning of the graphviz block, and at the 69 beginning of their respective lines. They must also not contain 70 any extra whitespace surrounding the = sign. 71 72 """ 73 74 # Change this to the directory that the Graphviz binaries (dot, neato, etc.) 75 # are installed in. 76 77 BINARY_PATH = '/usr/bin' 78 79 import os 80 import sys 81 import base64 82 import string 83 import exceptions 84 import codecs 85 import subprocess 86 import time 87 import sha 88 89 from MoinMoin import config 90 from MoinMoin.action import AttachFile 91 from MoinMoin import log 92 from MoinMoin import wikiutil 93 94 logging = log.getLogger(__name__) 95 96 class GraphVizError(exceptions.RuntimeError): 97 pass 98 99 100 Dependencies = [] 101 102 class Parser: 103 """Uses the Graphviz programs to create a visualization of a graph.""" 104 105 FILTERS = ['dot', 'neato', 'twopi', 'circo', 'fdp'] 106 IMAGE_FORMATS = ['png', 'gif'] 107 OBJECT_FORMATS = ['svg', 'svgz'] 108 OUTPUT_FORMATS = IMAGE_FORMATS + OBJECT_FORMATS 109 FORMATS = OUTPUT_FORMATS + \ 110 ['ps', 'fig', 'mif', 'hpgl', 'pcl', 'dia', 'imap', 'cmapx'] 111 extensions = [] 112 Dependencies = Dependencies 113 114 def __init__(self, raw, request, **kw): 115 self.raw = raw 116 self.request = request 117 118 def format(self, formatter): 119 """ Send the text. """ 120 121 request = self.request 122 page = request.page 123 _ = request.getText 124 125 request.flush() # to identify error text 126 127 self.filter = Parser.FILTERS[0] 128 self.format = 'png' 129 self.cmapx = None 130 131 raw_lines = self.raw.splitlines() 132 for l in raw_lines: 133 if not l[0:2] == '//': 134 break 135 if l.lower().startswith('//filter='): 136 tmp = l.split('=', 1)[1].lower() 137 if tmp in Parser.FILTERS: 138 self.filter = tmp 139 else: 140 logging.warn('unknown filter %s' % tmp) 141 elif l.lower().startswith('//format='): 142 tmp = l.split('=', 1)[1] 143 if tmp in Parser.FORMATS: 144 self.format = tmp 145 elif l.lower().startswith('//cmapx='): 146 self.cmapx = wikiutil.escape(l.split('=', 1)[1]) 147 148 if not self.format in Parser.OUTPUT_FORMATS: 149 raise NotImplementedError, "only formats %s are currently supported" % \ 150 Parser.OUTPUT_FORMATS 151 152 if self.cmapx: 153 if not self.format in Parser.IMAGE_FORMATS: 154 logging.warn('format %s is incompatible with cmapx option' % self.format) 155 self.cmapx = None 156 157 img_name = 'graphviz_%s.%s' % (sha.new(self.raw).hexdigest(), self.format) 158 159 self.pagename = formatter.page.page_name 160 url = AttachFile.getAttachUrl(self.pagename, img_name, request) 161 self.attach_dir=AttachFile.getAttachDir(request,self.pagename,create=1) 162 163 self.delete_old_graphs(formatter) 164 165 if not os.path.isfile(self.attach_dir + '/' + img_name): 166 self.graphviz(self.raw, fn='%s/%s' % (self.attach_dir, img_name)) 167 168 if self.format in Parser.IMAGE_FORMATS: 169 if self.cmapx: 170 request.write('\n' + self.graphviz(self.raw, format='cmapx') + '\n') 171 request.write(formatter.image(src="%s" % url, usemap="#%s" % self.cmapx)) 172 else: 173 request.write(formatter.image(src="%s" % url, alt="graphviz image")) 174 else: 175 request.write(formatter.transclusion(1, data=url)) 176 request.write(formatter.text(_("graphviz image"))) 177 request.write(formatter.transclusion(0)) 178 179 def delete_old_graphs(self, formatter): 180 page_info = formatter.page.lastEditInfo() 181 try: 182 page_date = page_info['time'] 183 except exceptions.KeyError, ex: 184 return 185 attach_files = AttachFile._get_files(self.request, self.pagename) 186 for chart in attach_files: 187 if chart.find('graphviz_') == 0 and chart[chart.rfind('.')+1:] in Parser.FORMATS: 188 fullpath = os.path.join(self.attach_dir, chart).encode(config.charset) 189 st = os.stat(fullpath) 190 chart_date = self.request.user.getFormattedDateTime(st.st_mtime) 191 if chart_date < page_date : 192 os.remove(fullpath) 193 else : 194 continue 195 196 def graphviz(self, graph_def, fn=None, format=None): 197 if not format: 198 format = self.format 199 if fn: 200 p = subprocess.Popen(['%s/%s' % (BINARY_PATH, self.filter), '-T%s' % format, '-o', fn], shell=False, \ 201 stdin=subprocess.PIPE, \ 202 stderr=subprocess.PIPE) 203 else: 204 p = subprocess.Popen(['%s/%s' % (BINARY_PATH, self.filter), '-T%s' % format], shell=False, \ 205 stdin=subprocess.PIPE, \ 206 stdout=subprocess.PIPE, \ 207 stderr=subprocess.PIPE) 208 209 p.stdin.write(graph_def) 210 p.stdin.flush() 211 p.stdin.close() 212 213 p.wait() 214 215 if not fn: 216 output = p.stdout.read() 217 218 errors = p.stderr.read() 219 if len(errors) > 0: 220 raise GraphVizError, errors 221 222 p = None 223 224 if fn: 225 return None 226 else: 227 return output 228