1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/graphviz.py Mon Sep 19 01:16:32 2011 +0200
1.3 @@ -0,0 +1,219 @@
1.4 +# -*- coding: iso-8859-1 -*-
1.5 +"""
1.6 + MoinMoin - Graphviz Parser
1.7 + Based loosely on GNUPLOT parser by MoinMoin:KwonChanYoung
1.8 +
1.9 + @copyright: 2008 Wayne Tucker
1.10 + @license: GNU GPL, see COPYING for details.
1.11 +"""
1.12 +
1.13 +"""
1.14 + BASIC USAGE:
1.15 +
1.16 + embed a visualization of a graph in a wiki page:
1.17 +
1.18 +{{{#!graphviz
1.19 +digraph G {
1.20 + A -> B;
1.21 +};
1.22 +}}}
1.23 +
1.24 + ADVANCED USAGE:
1.25 +
1.26 + This parser will check the first lines of the Graphviz data for C++ style
1.27 + comments instructing it to use a different filter (dot, neato, twopi,
1.28 + circo, or fdp - see http://graphviz.org/ for more info), use a different
1.29 + format for the output (see the FORMATS list in the Parser class below),
1.30 + or to generate and pass a client-side image map.
1.31 +
1.32 + Options:
1.33 + filter - the filter to use (see Parser.FILTERS)
1.34 + format - the output format (see Parser.FORMATS)
1.35 + cmapx - the map name to use for the client-side image map. Must match
1.36 + the graph name in the graph definition and shouldn't conflict
1.37 + with any other graphs that are used on the same page.
1.38 +
1.39 + embed a visualization of a graph in a wiki page, using the dot filter and
1.40 + providing a client-side image map (the filter=dot and format=png options are
1.41 + redundant since those are the defaults for this parser):
1.42 +
1.43 +{{{#!graphviz
1.44 +//filter=dot
1.45 +//format=png
1.46 +//cmapx=DocumentationMap
1.47 +digraph DocumentationMap {
1.48 + FrontPage [href="FrontPage", root=true];
1.49 + HelpOnEditing [href="HelpOnEditing"];
1.50 + SyntaxReference [href="SyntaxReference"];
1.51 + WikiSandBox [href="WikiSandBox", color="grey"];
1.52 + MoinMoin [href="http://moinmo.in"];
1.53 + FrontPage -> WikiSandBox;
1.54 + FrontPage -> MoinMoin;
1.55 + WikiSandBox -> HelpOnEditing;
1.56 + WikiSandBox -> SyntaxReference;
1.57 + SyntaxReference -> FrontPage;
1.58 +};
1.59 +}}}
1.60 +
1.61 +
1.62 + KNOWN BUGS:
1.63 + - Hasn't been thoroughly checked for potential methods of injecting
1.64 + arbitrary HTML into the output.
1.65 + - Only compatible with HTML rendering
1.66 + - May not use all of the MoinMoin interfaces properly - this is a
1.67 + quick hack based on looking at an example and digging through the
1.68 + MoinMoin source. The MoinMoin development docs haven't been
1.69 + consulted (yet).
1.70 + - Only image formats (png and gif) are currently implemented
1.71 + - Comments must start at the beginning of the graphviz block, and at the
1.72 + beginning of their respective lines. They must also not contain
1.73 + any extra whitespace surrounding the = sign.
1.74 +
1.75 +"""
1.76 +
1.77 +# Change this to the directory that the Graphviz binaries (dot, neato, etc.)
1.78 +# are installed in.
1.79 +
1.80 +BINARY_PATH = '/usr/bin'
1.81 +
1.82 +import os
1.83 +import sys
1.84 +import base64
1.85 +import string
1.86 +import exceptions
1.87 +import codecs
1.88 +import subprocess
1.89 +import time
1.90 +import sha
1.91 +
1.92 +from MoinMoin import config
1.93 +from MoinMoin.action import AttachFile
1.94 +from MoinMoin import log
1.95 +from MoinMoin import wikiutil
1.96 +
1.97 +logging = log.getLogger(__name__)
1.98 +
1.99 +class GraphVizError(exceptions.RuntimeError):
1.100 + pass
1.101 +
1.102 +
1.103 +Dependencies = []
1.104 +
1.105 +class Parser:
1.106 + """Uses the Graphviz programs to create a visualization of a graph."""
1.107 +
1.108 + FILTERS = ['dot', 'neato', 'twopi', 'circo', 'fdp']
1.109 + IMAGE_FORMATS = ['png', 'gif']
1.110 + FORMATS = IMAGE_FORMATS + ['ps', 'svg', 'svgz', 'fig', 'mif', \
1.111 + 'hpgl', 'pcl', 'dia', 'imap', 'cmapx']
1.112 + extensions = []
1.113 + Dependencies = Dependencies
1.114 +
1.115 + def __init__(self, raw, request, **kw):
1.116 + self.raw = raw
1.117 + self.request = request
1.118 +
1.119 + def format(self, formatter):
1.120 + """ Send the text. """
1.121 + self.request.flush() # to identify error text
1.122 +
1.123 + self.filter = Parser.FILTERS[0]
1.124 + self.format = 'png'
1.125 + self.cmapx = None
1.126 +
1.127 + raw_lines = self.raw.splitlines()
1.128 + for l in raw_lines:
1.129 + if not l[0:2] == '//':
1.130 + break
1.131 + if l.lower().startswith('//filter='):
1.132 + tmp = l.split('=', 1)[1].lower()
1.133 + if tmp in Parser.FILTERS:
1.134 + self.filter = tmp
1.135 + else:
1.136 + logging.warn('unknown filter %s' % tmp)
1.137 + elif l.lower().startswith('//format='):
1.138 + tmp = l.split('=', 1)[1]
1.139 + if tmp in Parser.FORMATS:
1.140 + self.format = tmp
1.141 + elif l.lower().startswith('//cmapx='):
1.142 + self.cmapx = wikiutil.escape(l.split('=', 1)[1])
1.143 +
1.144 + if not self.format in Parser.IMAGE_FORMATS:
1.145 + raise NotImplementedError, "only formats %s are currently supported" % Parser.IMAGE_FORMATS
1.146 +
1.147 + if self.cmapx:
1.148 + if not self.format in Parser.IMAGE_FORMATS:
1.149 + logging.warn('format %s is incompatible with cmapx option' % self.format)
1.150 + self.cmapx = None
1.151 +
1.152 + img_name = 'graphviz_%s.%s' % (sha.new(self.raw).hexdigest(), self.format)
1.153 +
1.154 + self.pagename = formatter.page.page_name
1.155 + url = AttachFile.getAttachUrl(self.pagename, img_name, self.request)
1.156 + self.attach_dir=AttachFile.getAttachDir(self.request,self.pagename,create=1)
1.157 +
1.158 + self.delete_old_graphs(formatter)
1.159 +
1.160 + if not os.path.isfile(self.attach_dir + '/' + img_name):
1.161 + self.graphviz(self.raw, fn='%s/%s' % (self.attach_dir, img_name))
1.162 +
1.163 + if self.format in Parser.IMAGE_FORMATS:
1.164 + if self.cmapx:
1.165 + self.request.write('\n' + self.graphviz(self.raw, format='cmapx') + '\n')
1.166 + self.request.write(formatter.image(src="%s" % url, usemap="#%s" % self.cmapx))
1.167 + else:
1.168 + self.request.write(formatter.image(src="%s" % url, alt="graphviz image"))
1.169 + else:
1.170 + # TODO: read the docs and figure out how to do this correctly
1.171 + self.request.write(formatter.attachment_link(True, url=url))
1.172 +
1.173 + def delete_old_graphs(self, formatter):
1.174 + page_info = formatter.page.lastEditInfo()
1.175 + try:
1.176 + page_date = page_info['time']
1.177 + except exceptions.KeyError, ex:
1.178 + return
1.179 + attach_files = AttachFile._get_files(self.request, self.pagename)
1.180 + for chart in attach_files:
1.181 + if chart.find('graphviz_') == 0 and chart[chart.rfind('.')+1:] in Parser.FORMATS:
1.182 + fullpath = os.path.join(self.attach_dir, chart).encode(config.charset)
1.183 + st = os.stat(fullpath)
1.184 + chart_date = self.request.user.getFormattedDateTime(st.st_mtime)
1.185 + if chart_date < page_date :
1.186 + os.remove(fullpath)
1.187 + else :
1.188 + continue
1.189 +
1.190 + def graphviz(self, graph_def, fn=None, format=None):
1.191 + if not format:
1.192 + format = self.format
1.193 + if fn:
1.194 + p = subprocess.Popen(['%s/%s' % (BINARY_PATH, self.filter), '-T%s' % format, '-o', fn], shell=False, \
1.195 + stdin=subprocess.PIPE, \
1.196 + stderr=subprocess.PIPE)
1.197 + else:
1.198 + p = subprocess.Popen(['%s/%s' % (BINARY_PATH, self.filter), '-T%s' % format], shell=False, \
1.199 + stdin=subprocess.PIPE, \
1.200 + stdout=subprocess.PIPE, \
1.201 + stderr=subprocess.PIPE)
1.202 +
1.203 + p.stdin.write(graph_def)
1.204 + p.stdin.flush()
1.205 + p.stdin.close()
1.206 +
1.207 + p.wait()
1.208 +
1.209 + if not fn:
1.210 + output = p.stdout.read()
1.211 +
1.212 + errors = p.stderr.read()
1.213 + if len(errors) > 0:
1.214 + raise GraphVizError, errors
1.215 +
1.216 + p = None
1.217 +
1.218 + if fn:
1.219 + return None
1.220 + else:
1.221 + return output
1.222 +