Attachment 'graphviz_0.1.py'
Download 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 FORMATS = IMAGE_FORMATS + ['ps', 'svg', 'svgz', 'fig', 'mif', \
108 'hpgl', 'pcl', 'dia', 'imap', 'cmapx']
109 extensions = []
110 Dependencies = Dependencies
111
112 def __init__(self, raw, request, **kw):
113 self.raw = raw
114 self.request = request
115
116 def format(self, formatter):
117 """ Send the text. """
118 self.request.flush() # to identify error text
119
120 self.filter = Parser.FILTERS[0]
121 self.format = 'png'
122 self.cmapx = None
123
124 raw_lines = self.raw.splitlines()
125 for l in raw_lines:
126 if not l[0:2] == '//':
127 break
128 if l.lower().startswith('//filter='):
129 tmp = l.split('=', 1)[1].lower()
130 if tmp in Parser.FILTERS:
131 self.filter = tmp
132 else:
133 logging.warn('unknown filter %s' % tmp)
134 elif l.lower().startswith('//format='):
135 tmp = l.split('=', 1)[1]
136 if tmp in Parser.FORMATS:
137 self.format = tmp
138 elif l.lower().startswith('//cmapx='):
139 self.cmapx = wikiutil.escape(l.split('=', 1)[1])
140
141 if not self.format in Parser.IMAGE_FORMATS:
142 raise NotImplementedError, "only formats %s are currently supported" % Parser.IMAGE_FORMATS
143
144 if self.cmapx:
145 if not self.format in Parser.IMAGE_FORMATS:
146 logging.warn('format %s is incompatible with cmapx option' % self.format)
147 self.cmapx = None
148
149 img_name = 'graphviz_%s.%s' % (sha.new(self.raw).hexdigest(), self.format)
150
151 self.pagename = formatter.page.page_name
152 url = AttachFile.getAttachUrl(self.pagename, img_name, self.request)
153 self.attach_dir=AttachFile.getAttachDir(self.request,self.pagename,create=1)
154
155 self.delete_old_graphs(formatter)
156
157 if not os.path.isfile(self.attach_dir + '/' + img_name):
158 self.graphviz(self.raw, fn='%s/%s' % (self.attach_dir, img_name))
159
160 if self.format in Parser.IMAGE_FORMATS:
161 if self.cmapx:
162 self.request.write('\n' + self.graphviz(self.raw, format='cmapx') + '\n')
163 self.request.write(formatter.image(src="%s" % url, usemap="#%s" % self.cmapx))
164 else:
165 self.request.write(formatter.image(src="%s" % url, alt="graphviz image"))
166 else:
167 # TODO: read the docs and figure out how to do this correctly
168 self.request.write(formatter.attachment_link(True, url=url))
169
170 def delete_old_graphs(self, formatter):
171 page_info = formatter.page.lastEditInfo()
172 try:
173 page_date = page_info['time']
174 except exceptions.KeyError, ex:
175 return
176 attach_files = AttachFile._get_files(self.request, self.pagename)
177 for chart in attach_files:
178 if chart.find('graphviz_') == 0 and chart[chart.rfind('.')+1:] in Parser.FORMATS:
179 fullpath = os.path.join(self.attach_dir, chart).encode(config.charset)
180 st = os.stat(fullpath)
181 chart_date = self.request.user.getFormattedDateTime(st.st_mtime)
182 if chart_date < page_date :
183 os.remove(fullpath)
184 else :
185 continue
186
187 def graphviz(self, graph_def, fn=None, format=None):
188 if not format:
189 format = self.format
190 if fn:
191 p = subprocess.Popen(['%s/%s' % (BINARY_PATH, self.filter), '-T%s' % format, '-o', fn], shell=False, \
192 stdin=subprocess.PIPE, \
193 stderr=subprocess.PIPE)
194 else:
195 p = subprocess.Popen(['%s/%s' % (BINARY_PATH, self.filter), '-T%s' % format], shell=False, \
196 stdin=subprocess.PIPE, \
197 stdout=subprocess.PIPE, \
198 stderr=subprocess.PIPE)
199
200 p.stdin.write(graph_def)
201 p.stdin.flush()
202 p.stdin.close()
203
204 p.wait()
205
206 if not fn:
207 output = p.stdout.read()
208
209 errors = p.stderr.read()
210 if len(errors) > 0:
211 raise GraphVizError, errors
212
213 p = None
214
215 if fn:
216 return None
217 else:
218 return output
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.