# -*- coding: iso-8859-1 -*-
'''
main.py -- actual implementation of all macros in the package.
----
Copyright (C) 2007-2008 Zoran Isailovski
Licensed under the Apache License, Version 2.0 (see http://www.apache.org/licenses/LICENSE-2.0)
or, at your option, under the terms described in the file LICENSE.txt found in the root of the
distribution of this package (as far as it denotes a different license then the above).
----
'''
__todo__ = '''\
'''
import sys, os
DEBUG = False
# Graphviz executables - names (dot, neato, twopi, fdp) and location
GRAPHVIZ_TOOLS_DIR = r'' # leave empty if the executables are on your PATH
GRAPHVIZ_TOOLS = 'dot twopi neato circo fdp'.split()
DEFAULT_TOOL = 'DOT' # keep this upper case to distinguish from explicit tool name
##EXE_SUFFIX = (sys.platform == 'win32' and '.exe' or '') # suffix for executables on your platform
EXE_SUFFIX = '' # actually, omitting the suffix should work on win32, too, if PATHEXT is properly configured
# Absolute location of the graphviz executables (dot, neato, twopi, fdp).
# Calculated from the above options - don't modify!
GRAPHVIZ = dict(
[ (k, os.path.join(GRAPHVIZ_TOOLS_DIR, '%s%s' % (k, EXE_SUFFIX)))
for k in GRAPHVIZ_TOOLS ])
del k
######################################################################
##
## Moin Interface
##
######################################################################
import re
from cStringIO import StringIO
from MoinMoin import config
##import MoinLegacy # re-inforce backward compatibility
#from umoin.MoinLegacy import wiki
from MoinMoin.parser import text_moin_wiki as wiki
from MoinMoin.version import release as moinVersion
moinVersion = tuple(map(int, moinVersion.split('.')))
#UID = 'BB962F5E-DB8E-424C-8E4D-D2B53286D6F3'
class GraphvizRenderError(Exception):
pass
class GraphvizSyntaxError(GraphvizRenderError):
pass
class Parser:
"""
MoinMoin GraphViz parser.
"""
extensions = []
Dependencies = []
def __init__(self, raw, request, **kw):
self.args = kw.get('format_args', 'dot')
(tool, ) = parseArguments(self.args)
self.raw = raw
self.request = request
p = request.formatter.page
self.renderer = Renderer(tool, targetdir=p.getPagePath('attachments'), encoding=config.charset)
def format(self, formatter):
append_text = ''
try:
s = self.renderer.render(self.raw)
except GraphvizSyntaxError, e:
s = e.imagefilename
#eliminate source path in the error message, which annoys users
append_text = "{{{%s}}}" % str(e).replace(e.dotfilename, "")
except GraphvizRenderError, e:
self.request.write("GraphViz:
%s
" % e)
return
fmt = '{{attachment:%s}}' if moinVersion >= (1,6) else 'attachment:%s'
fmt += append_text
s = wiki2html(self.request, fmt % os.path.basename(s))
if DEBUG: print '[TRACE] attachment html:', s
self.request.write(s)
def parseArguments(s):
progs = '|'.join(GRAPHVIZ_TOOLS)
pattern = r'^\s*(?P%s)?\s*$' % progs
m = re.match(pattern, s, re.IGNORECASE)
##assert m, 'Invalid parser arguments'
program = m and m.group('tool') or DEFAULT_TOOL
return (program, )
def wiki2html(request, text):
stream = StringIO()
request.redirect(stream)
parser = wiki.Parser(text, request)
parser.format(request.formatter)
html = stream.getvalue()
request.redirect()
stream.close(); del stream
return html
if __name__ == '__main__':
print parseArguments(' fdp ')
print parseArguments('dot')
print parseArguments('')
print parseArguments('xxx')
######################################################################
##
## Rendering Stuff
##
######################################################################
import sys
import sha
import string
from subprocess import Popen, PIPE
try: __file = __file__
except NameError: __file = sys.argv[0]
##__filedir = os.path.join(os.path.dirname(__file))
##__moindir = os.path.abspath(os.path.join(__filedir, '../../../../..'))
##BASE_DIR = __moindir
##del __filedir, __moindir
##if DEBUG: print '[TRACE] BASE_DIR =', BASE_DIR
class Snippets:
'''
Snippets are graph script fragments that can be included in the graph.
To include snippet FOO, use $FOO in the graph.
'''
STD_GRAPH_HEADER = '''overlap=false
graph [ charset="utf-8" ] // hint from an anonimous user
node [fontname=Verdana fontsize=10 style=filled fillcolor=azure color=steelblue fontcolor=steelblue]
edge [fontname=Verdana fontsize=8 color=steelblue fontcolor=steelblue]'''
ATTEMPT_TO_JOIN_EDGES = 'concentrate=true'
LEFT_TO_RIGHT = 'rankdir=LR'
SAMPLE_SCRIPT = r'''
digraph XXX {
$STD_GRAPH_HEADER
A -> {B C}; B -> C
}
'''
class Renderer:
def __init__(self, tool='dot', targetdir='', format='png', encoding='utf-8'):
tool = GRAPHVIZ.get(tool) or GRAPHVIZ['dot']
# do not longer assume relative paths are relative to the moin installation dir
##tool = os.path.join(BASE_DIR, tool)
tool = os.path.normpath(tool)
# executables may be reachable via PATH, so os.path.isfile is not sufficient
# instead of checking for file existence up front,
# check exit code after command execution (see oscmd)
##assert os.path.isfile(tool), 'File not found: %r' % tool
self.toolpath = tool
self.toolname = os.path.splitext(os.path.basename(tool))[0]
self.targetdir = targetdir
self.format = format
self.encoding = encoding # see method normalizedScript below
def render(self, script):
script = self.normalizedScript(script)
uid = self.hashFor(script)
gname = graphName(script)
imagefilename = 'graphviz-%s-%s.%s' % (gname, uid, self.format)
imagefilename = os.path.join(self.targetdir, imagefilename)
##if DEBUG: print '[TRACE] imagefilename:', imagefilename
if not os.path.isfile(imagefilename):
if DEBUG: print '[TRACE] creating graph image:', imagefilename
dotfilename = '%s-%s.%s' % ('graph', uid, 'graphviz.txt')
dotfilename = os.path.join(self.targetdir, dotfilename)
fwrite(dotfilename, script)
try:
renderGraphImage(self.toolpath, self.format, imagefilename, dotfilename)
except GraphvizRenderError, e:
if os.path.exists(imagefilename):
#imagefilename exists means nothing terriblely happeded,
#just little cases about dot syntax.
if DEBUG: print "[TRACE] Syntax Error"
e = GraphvizSyntaxError(e)
e.imagefilename = imagefilename
e.dotfilename = dotfilename
raise e
finally:
os.remove(dotfilename)
return imagefilename
def normalizedScript(self, script=SAMPLE_SCRIPT):
##return script.strip() % vars(Snippets) # for syntax %(name)s
##return string.Template(script.strip()).safe_substitute(vars(Snippets)) # for syntax $name
v = vars(Snippets)
s = string.Template(script.strip() % v).safe_substitute(v) # for both syntaxes
# the following is a hint from an anonimous user, the purpose of which I do not quite
# understand yet. it should fix a "unicode problem", but it is unclear what the problem
# actually is and how it occurs. I'll take the patch in just in case, in the hope that
# it really fixes something and someone can explain what that something is. -- ZI, 2008-04-20
return unicode(s).encode(self.encoding)
#return s
def hashFor(self, content):
return sha.new(content).hexdigest()
def graphName(script):
##m = re.match(r'^(?:\n|\s)*(?:di)?graph\s*(\w*)', script) # allows spaces but no comments at beginning of script
m = re.search(r'^(?:di)?graph\s+(\w*)', script, re.M)
if m is None:
raise GraphvizRenderError( \
"Could not derive graph name from graph script. Check the syntax, please!")
return m.group(1)
def renderGraphImage(tool, format, imagefilename, dotfilename):
cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" "%(dotfilename)s"' % locals()
if DEBUG: print '[TRACE] executing:', cmd
oscmd(cmd)
######################################################################
##
## File IO and OS Auxiliaries
##
######################################################################
def fread(fname):
f = open(fname, 'rt')
try:
return f.read()
finally:
f.close()
def fwrite(fname, content):
f = open(fname, 'wt')
try:
return f.write(str(content))
finally:
f.close()
def oscmd(cmd):
'''instead of simply calling os.system(cmd)
capture stderr and raise GraphvizRenderError if exit code != 0
'''
p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
bufsize=1024, close_fds=True)
stdo, stde = p.communicate()
if p.returncode != 0:
raise GraphvizRenderError("%s\n%s" % (stdo, stde))
######################################################################
##
## TEST
##
######################################################################
TEST_SCRIPT = '''\
digraph X
{
$STD_GRAPH_HEADER
subgraph cluster1 {
label="global space"
node [fillcolor=cornsilk]
P Q
}
B -> C
C -> A
Q -> A
}
'''
##if __name__ == '__main__':
## print
## r = Renderer('fdp')
## s = r.render(TEST_SCRIPT)
## print s
## os.startfile(s)