screenshot-GraphOutput.png

Purpose

This extension provides generic access to the GraphViz Graph Visualization Toolset.

Download

MoinGraphViz-1.0rc4-brn-1.9.x.comp.zip

Branched release, see #ChangeLog

-- Patched By BOYPT 2010-11-18 15:05:00

MoinGraphViz-1.0rc4.zip

Latest release, see #ChangeLog

-- ZoranIsailovski 2009-05-20 23:05:30

Dependencies: GraphViz Toolset

Installation And Configuration

Install

  1. unzip the archive.
  2. copy wiki directory into your data path.

  3. copy umoin directory into you python path, recommend MoinMoin/support/.(For Moin 1.7 and above.)

  4. Example:
           $ unzip MoinGraphViz-1.0rc4.zip
           $ cp -r MoinGraphViz-1.0rc4/wiki /path/to/my-moin/
           $ cp -r MoinGraphViz-1.0rc4/umoin /path/to/MoinMoin/support/
    

The archive already contains the required directory layout and should add/create the appropriate files where they belong to. If you're using local wiki, just copy the umoin directory into MoinMoin/support/ under your moin directory.

Also, don't forget to download and install/unpack the GraphViz toolset, if there isn't one already on you computer.

Configure

The extension needs to know where your GraphViz toolset resides, and what the default GraphViz tool is. Normally this is not needed if you're in a linux distro, and install from packages(eg. via apt-get install graphviz).

Specify the graphviz tools location In module plugin/parser/MoinGraphViz/main.py

Features

DOT Language

Graphs are specified by graph scripts in the parser section. Graph scripts are written in the DOT language.

Free Choice of GraphViz Layouter

The parser is invoked by

{{{#!GraphViz <layouter>
<graph script>
}}}

where layouter is optional and one of dot, neato, twopi, fdp, circo (generally all layouters in the GraphViz toolset are supported). If no layouter is specified, the configured DEFAULT_TOOL is invoked.

Graph Snippets

The default GraphViz formatting is not particularly appealing. Getting the default formatting look "right" is a cumbersome repetitive task of writing the same chunk of graph code in every graph you design. Clearly, including that code is better then retyping it.

In order to support this need, the extension features a graph snippet concept. Snippets contain commonly used pieces of graph description code. They are included in the graph using the notation $Snippet.

For example, the graph image at the top of this page has been generated by:

{{{#!GraphViz dot
digraph SomeGraph {
  $STD_GRAPH_HEADER
  A -> { B C }
  B -> C
}
}}}

The available snippet set is represented by attributes of the class Snippets (in module plugin/parser/MoinGraphViz/main.py). Currently, there are the snippets:

Attachment Cleanup Support

When a graph is rendered, an image file is created in the page's attachment area. When a graph is changed, another image is created, and the old image is not deleted. (It will be reused in case the graph script is reverted to the previous state.)

This potentially leads to a bunch of obsolete graph images in your attachment directory.

It is possible to delete these images one by one, vie moin's attachments action, but this is too cumbersome - over time, there may be just too many such files.

Graph image cleanup is supported with the GraphVizCleanup action. The action is based on the upcoming Attachment Deletions Actions package, a prelimiary version of which is (partially) included herein.

When the action is invoked, it wil detect attachments that match the GraphViz parser's specific naming pattern, and prompt you for deletion:

screenshot-x1-DeleteFilesDialog.png

Of course, it is possible, though unlikely, that there are "foreign" attachments that do match the naming pattern, so take a closer look at the file names presented, and uncheck those you don't want deleted. Then press the button to delete the attachments. You'll see a completetion message like this:

screenshot-x2-DeletedFilesInfo.png

Caveats

Unique Graph Names

When a graph image is rendered, the image file name is mangled with the graph name. (The graph name is the name that follows the graph or digraph keyword at the beginning of the graph script.) Thus, in order to prevent from image file name clashes, graphs on a page should have unique names.

Using Without Attachments

It is possible to use the plugin with attachments disabled on moin installation with minimal changes to the plugin.

You need to create a directory writable by your www user and accessible on the web to store cache of rendered graphs. Then you need to apply the following patch and replace values of REALDIR and WWWDIR with suitable ones.

   1 diff -r 7d8bd5c08afe -r b786e3db966b wiki/data/plugin/parser/MoinGraphViz/main.py
   2 --- a/wiki/data/plugin/parser/MoinGraphViz/main.py      Fri Dec 24 17:18:07 2010 +0300
   3 +++ b/wiki/data/plugin/parser/MoinGraphViz/main.py      Fri Dec 24 17:57:23 2010 +0300
   4 @@ -14,6 +14,9 @@
   5 
   6  DEBUG = False
   7 
   8 +REALDIR = '/path/to/some/accessible/directory'
   9 +WWWDIR = '/www/name/of/that/directory'
  10 +
  11  # Graphviz executables - names (dot, neato, twopi, fdp) and location
  12  GRAPHVIZ_TOOLS_DIR = r'' # leave empty if the executables are on your PATH
  13  GRAPHVIZ_TOOLS = 'dot twopi neato circo fdp'.split()
  14 @@ -66,7 +69,7 @@
  15          self.raw = raw
  16          self.request = request
  17          p = request.formatter.page
  18 -        self.renderer = Renderer(tool, targetdir=p.getPagePath('attachments'), encoding=config.charset)
  19 +        self.renderer = Renderer(tool, targetdir=REALDIR, encoding=config.charset)
  20 
  21      def format(self, formatter):
  22          append_text = ''
  23 @@ -79,11 +82,11 @@
  24          except GraphvizRenderError, e:
  25              self.request.write("<strong class=\"error\">GraphViz: </strong><pre>%s</pre>" % e)
  26              return
  27 -        fmt = '{{attachment:%s}}' if moinVersion >= (1,6) else 'attachment:%s'
  28 -        fmt += append_text
  29 -        s = wiki2html(self.request, fmt % os.path.basename(s))
  30 -        if DEBUG: print '[TRACE] attachment html:', s
  31 -        self.request.write(s)
  32 +        text = [
  33 +          formatter.image("%s/%s" % (WWWDIR, os.path.basename(s))),
  34 +          wiki2html(self.request, append_text),
  35 +        ]
  36 +        self.request.write(u"\n".join(text))
  37 
  38 
  39  def parseArguments(s):

ChangeLog

1.0rc4-branched

* Added compatibility to MoinMoin 1.9.x;

  • Added dot syntax error message output.
  • Unicode character supported by default.
1.0rc4

. Added erroneously omitted "if DEBUG" condition to trace print statements, and preset DEBUG to False (instead of True). This constitues a workaround for an issue, reported and investigated by Krzysztof Stryjek. Many thanks, Krzysztof!

1.0rc3

. The plug-in has been broken by backward incompatible changes to Moin's API in version 1.7. (I guess, the concept of backward compatibility is for wimps only ;-) ) Fortunately, SamMorris and AdamBregenzer have supplied patches to adapt the code to the new API - thanks a lot, guys! I have integrated their changes in a way that still preserves backward compatbility of the plug-in (checked against 1.3.4, 1.6.4, 1.7.2 and 1.8.2).

  • This required the addition of a "backward compatibility layer" in the shape of the python module MoinLegacy.py. The module must be accessible for import MoinLegacy. I usually put it in the Moin installation directory of my MoinDesktopEdition. The technique I've applied is "backward compatibility injection": Removed (and renamed) functions and methods are "injected" back again externally. The python module responsible for the injection is umoin.MoinLegacy.py. It should be on the python path (side by side with the wiki directory should be fine). The module performs the injection upon import, and provides unified alias names for renamed modules. Currently, it only covers the part of the API that affects MoinGraphViz and MoinAttachmentsDeletion, but it should be extended as required to make other plug-ins work. In addition, I have modified the way the GraphViz tools are accessed. The default implementation does not preset the tools' directory and file name suffix, but assumes (a) that the tools are on the PATH, and (b) that a suffix, if any, will be handled by the OS implicitly. This should work on both windows and *nix likewise. Note that a specific path and file name suffix can still be specified via configuration variables, it's just that they are now empty by default.

1.0rc2

* integrated fixes and suggestions from the community (see "Encoding Problems" on the discussion page)

  • the suffix .exe is only used if sys.platform == 'win32' (and there's a preset that can be easily changed)

  • prepared to integrate the anonimous patch on the /Discussion page
  • $STD_GRAPH_HEADER now includes a statement graph [ charset="utf-8" ]

  • generelized the cleanup action to work in contexts other then "GraphVizCleanup"

    • (on its way to a general attachment cleanup service)
1.0rc1
initial public release

References

There's been another extension targeting graph visualization, dot.py. I've tried to get it working, but with no success. Besides that, it seemed to have a fixed, pre-selected graph layouter, which was a no-go in my case.


Discussion

Discussions over here, on this subpage, please ;)

compatibility

1.9 compatibility

The minimun patch for GraphVizMoin1.0rc4 working with MoinMoin 1.9.x by BOYPT:

   1 --- ../MoinGraphViz-1.0rc4/umoin/MoinLegacy.py	2008-09-21 12:30:16.000000000 +0800
   2 +++ MoinMoin/support/umoin/MoinLegacy.py	2010-10-27 16:45:19.253333324 +0800
   3 @@ -26,7 +26,8 @@
   4          key = method.__name__
   5          if not hasattr(target, key): setattr(target, key, method)
   6  
   7 -    from MoinMoin import wikiutil, request
   8 +    from MoinMoin import wikiutil 
   9 +    from MoinMoin.web import request
  10  
  11      for method in (send_title, send_footer):
  12          install_method(method, wikiutil)
  13 @@ -34,6 +35,8 @@
  14      req = request.RequestBase
  15      if not hasattr(req, 'http_headers') and hasattr(req, 'emit_http_headers'):
  16          req.http_headers = req.emit_http_headers
  17 +    else:
  18 +        req.http_headers = lambda s:()
  19  
  20  inject_backward_compatibility()
  21  
  22 --- ../MoinGraphViz-1.0rc4/wiki/data/plugin/action/MoinAttachmentsDeletion/main.py	2008-11-20 03:57:48.000000000 +0800
  23 +++ wiki/data/plugin/action/MoinAttachmentsDeletion/main.py	2010-10-27 16:52:45.339999989 +0800
  24 @@ -90,8 +90,7 @@
  25      page = request.page
  26      folder = page.getPagePath('attachments')
  27  
  28 -    action = request.form.pop('action')
  29 -    items = request.form.items()
  30 +    items = [i for i in request.form.items() if i[0] != 'action']
  31  
  32      messages = []
  33      ##request.write('<pre>%s</pre>' % items)
minimun_graphviz4moin_fix_for_1.9.x.patch

Remember to copy umoin directory to MoinMoin/support under your installation.

1.8 compatibility

I've just installed your plugin on 1.8.x Moin. It's great. but I have one small problem: when image file is created is displayed twice. Any idea why it behaves in this way? I've patched original plugin with suggestions for 1.7 compability.

Thanks a lot for your help. -- Krzysztof Stryjek <admin AT bsdserwis DOT com>

  • Version 1.0rc3 should be compatible with both 1.7 and 1.8. On my box, it produces only one image. -- ZoranIsailovski 2009-05-20 23:07:36

1.7 compatibility

I can confirm this path works for my version that started on Moin 1.5, then the data dir got moved to Moin 1.9.1 - 2010-02-28 - Paul De Audney

I had to patch the parser to get it to work with 1.7.

   1 --- /tmp/gvm/wiki/data/plugin/parser/MoinGraphViz/main.py       2008-04-20 11:03:36.000000000 +0100
   2 +++ moin/data/plugin/parser/MoinGraphViz/main.py        2008-06-23 22:27:36.000000000 +0100
   3 @@ -15,10 +15,11 @@
   4  DEBUG = True
   5  
   6  # Graphviz executables - names (dot, neato, twopi, fdp) and location
   7 -GRAPHVIZ_TOOLS_DIR = 'tools'
   8 +GRAPHVIZ_TOOLS_DIR = '/usr/bin'
   9  GRAPHVIZ_TOOLS = 'dot twopi neato circo fdp'.split()
  10  DEFAULT_TOOL = 'DOT' # keep this upper case to distinguish from explicit tool name
  11  
  12 +import sys
  13  EXE_SUFFIX = (sys.platform == 'win32' and '.exe' or '') # suffix for executables on your platform
  14  
  15  # Absolute location of the graphviz executables (dot, neato, twopi, fdp).
  16 @@ -36,7 +37,7 @@
  17  import re
  18  from cStringIO import StringIO
  19  from MoinMoin import config
  20 -from MoinMoin.parser import wiki
  21 +from MoinMoin.parser import text_moin_wiki
  22  ##from MoinMoin.Page import Page
  23  
  24  #UID = 'BB962F5E-DB8E-424C-8E4D-D2B53286D6F3'
  25 @@ -60,7 +61,7 @@
  26          w = self.request.write
  27          ##w('<div style="border:3px ridge gray; padding:5px; width:95%; overflow:auto">')
  28          s = self.renderer.render(self.raw)
  29 -        s = wiki2html(self.request, 'attachment:%s' % os.path.basename(s))
  30 +        s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
  31          print '[TRACE] attachment URL:', s
  32          w(s)
  33          ##w('</div>')
  34 @@ -77,7 +78,7 @@
  35  def wiki2html(request, text):
  36      stream = StringIO()
  37      request.redirect(stream)
  38 -    parser =  wiki.Parser(text, request)
  39 +    parser =  text_moin_wiki.Parser(text, request)
  40      parser.format(request.formatter)
  41      html = stream.getvalue()
  42      request.redirect()

The MoinAttachmentsDeletion action seems to require more extensive changes. -- SamMorris 2008-06-23 22:09:00

Here is a patch for the MoinAttachmentsDeletion action for 1.7

   1 --- main.old.py 2008-09-16 18:01:19.000000000 -0400
   2 +++ main.py     2008-09-16 18:00:58.000000000 -0400
   3 @@ -27,8 +27,8 @@
   4  
   5  def execute(process, pagename, request):
   6      _ = request.getText
   7 -    request.http_headers()
   8 -    wikiutil.send_title(request, _(TITLE + ' for "%s"' % pagename), pagename=pagename)
   9 +    request.emit_http_headers()
  10 +    request.theme.send_title(_(TITLE + ' for "%s"' % pagename), pagename=pagename)
  11      try:
  12          try:
  13              process(request)
  14 @@ -39,7 +39,7 @@
  15              output = ''.join( output )
  16              request.write( ERROR_FORMAT % (TITLE, pagename, output) )
  17      finally:
  18 -        wikiutil.send_footer(request, pagename)
  19 +        request.theme.send_footer(pagename)
  20  
  21  ERROR_FORMAT = '''\
  22  <div class="traceback" style="background-color:red; color:lightyellow; font-weight: bold">

So far this and Sam's patch together are working for me. -- AdamBregenzer 2008-09-16 23:19:00

new feature

Changing the layouter vs. cached image

2009-09-29: When you change the layouter without changing the graph, the previously cached image (produced with the old layouter) will still be displayed.

I think the caching algorithm should take the layouter into account.

Client-side image map support (clickable nodes!)

This allows URLs to be attached to nodes. The maps are cached as text file attachments and deleted by the cleanup action. Here's a cool tip: you can autolink nodes to a specific URL pattern, i.e.:

digraph SomeGraph {
  $STD_GRAPH_HEADER
  node [URL="http://moinmo.in/\N"]
  MoinMoinWiki -> WikiEngine
  MoinMoinWiki -> GPL
  MoinMoinWiki -> WhyWikiWorks
  WikiEngine -> WikiEngineComparison
}

-- IzaakBranderhorst 2009-12-09 15:40:51

   1 --- MoinGraphViz-1.0rc4/wiki/data/plugin/parser/MoinGraphViz/main.py    2009-05-22 12:22:34.000000000 -0400
   2 +++ data/plugin/parser/MoinGraphViz/main.py     2009-12-09 15:29:04.000000000 -0500
   3 @@ -10,7 +10,7 @@
   4  '''
   5  __todo__ = '''\
   6  '''
   7 -import sys, os
   8 +import sys, os, re
   9  
  10  DEBUG = False
  11  
  12 @@ -64,9 +64,14 @@
  13      def format(self, formatter):
  14          w = self.request.write
  15          ##w('<div style="border:3px ridge gray; padding:5px; width:95%; overflow:auto">')
  16 -        s = self.renderer.render(self.raw)
  17 +        (imagefilename, cmapfilename) = self.renderer.render(self.raw)
  18          fmt = moinVersion >= (1,6) and '{{attachment:%s}}' or 'attachment:%s'
  19 -        s = wiki2html(self.request, fmt % os.path.basename(s))
  20 +        s = wiki2html(self.request, fmt % os.path.basename(imagefilename))
  21 +        if os.path.isfile(cmapfilename):
  22 +            cmap = fread(cmapfilename)
  23 +            if cmap:
  24 +                s = re.sub('<img ', '<img usemap="#%s" ' % os.path.basename(cmapfilename), s, 1)
  25 +                s += '<map name="%s">%s</map>' % (os.path.basename(cmapfilename), cmap)
  26          if DEBUG: print '[TRACE] attachment URL:', s
  27          w(s)
  28          ##w('</div>')
  29 @@ -159,6 +164,8 @@
  30          gname = graphName(script) 
  31          imagefilename = 'graphviz-%s-%s.%s' % (gname, uid, self.format)
  32          imagefilename = os.path.join(self.targetdir, imagefilename)
  33 +        cmapfilename = 'graphviz-%s-%s.txt' % (gname, uid)
  34 +        cmapfilename = os.path.join(self.targetdir, cmapfilename)
  35          ##if DEBUG: print '[TRACE] imagefilename:', imagefilename
  36          if not os.path.isfile(imagefilename):
  37              if DEBUG: print '[TRACE] creating graph image:', imagefilename
  38 @@ -166,10 +173,15 @@
  39              dotfilename = os.path.join(self.targetdir, dotfilename)
  40              fwrite(dotfilename, script)
  41              try:
  42 -                renderGraphImage(self.toolpath, self.format, imagefilename, dotfilename)
  43 +                renderGraphImage(self.toolpath, self.format, imagefilename, cmapfilename, dotfilename)
  44 +                # if the graph contains no URLs the imagemap file will be empty, we'll remove it to
  45 +                # avoid clutter
  46 +                cmap = fread(cmapfilename)
  47 +                if not cmap:
  48 +                    os.remove(cmapfilename)
  49              finally:
  50                  os.remove(dotfilename)
  51 -        return imagefilename
  52 +        return (imagefilename, cmapfilename)
  53  
  54      def normalizedScript(self, script=SAMPLE_SCRIPT):
  55          ##return script.strip() % vars(Snippets) # for syntax %(name)s
  56 @@ -192,8 +204,8 @@
  57      assert m, 'Could not derive graph name from graph script. Check the syntax, please!'
  58      return m.group(1)
  59  
  60 -def renderGraphImage(tool, format, imagefilename, dotfilename):
  61 -    cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" "%(dotfilename)s"' % locals()
  62 +def renderGraphImage(tool, format, imagefilename, cmapfilename, dotfilename):
  63 +    cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" -Tcmap -o"%(cmapfilename)s" "%(dotfilename)s"' % locals()
  64      if DEBUG: print '[TRACE] executing:', cmd
  65      oscmd(cmd)

   1 --- MoinGraphViz-1.0rc4/wiki/data/plugin/action/MoinAttachmentsDeletion/main.py 2008-11-20 03:57:48.000000000 -0500
   2 +++ data/plugin/action/MoinAttachmentsDeletion/main.py  2009-12-09 15:27:58.000000000 -0500
   3 @@ -66,7 +66,7 @@
   4  
   5  ######################################################################
   6  
   7 -GRAPHVIZ_FILE_PATTERN = re.compile( r'graphviz-\w+-\w{40}\.(?:png|gif|jpg)$' )
   8 +GRAPHVIZ_FILE_PATTERN = re.compile( r'graphviz-\w+-\w{40}\.(?:png|gif|jpg|txt)$' )
   9  
  10  def GraphVizCleanupAction(request):
  11      do(request, GRAPHVIZ_FILE_PATTERN, action='GraphVizCleanup', title='GraphViz Attachments')

image map support with jQuery (clickable nodes!)

  1. fixed parser/MoinGraphViz/main.py at first
    •    1 Index: tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py
         2 ===================================================================
         3 --- tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py (revision 16946)
         4 +++ tasks/wiki.KUP/MoinMoin/parser/MoinGraphViz/main.py (revision 17013)
         5 @@ -56,4 +56,5 @@
         6          p = request.formatter.page
         7          self.renderer = Renderer(tool, targetdir=p.getPagePath('attachments'), encoding=config.charset)
         8 +        self.attapath = p.getPagePath('attachments')
         9  
        10      def format(self, formatter):
        11 @@ -61,5 +62,18 @@
        12          ##w('<div style="border:3px ridge gray; padding:5px; width:95%; overflow:auto">')
        13          s = self.renderer.render(self.raw)
        14 -        s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
        15 +        #   100728 Zoom.Quiet fixed for include URL hotarea map define
        16 +        fImgName = os.path.basename(s)
        17 +        pfImgMap = "%s/%s.map"%(self.attapath,fImgName)
        18 +        #s = wiki2html(self.request, '{{attachment:%s}}' % os.path.basename(s))
        19 +        s = wiki2html(self.request, '{{attachment:%s|%s}}' % (os.path.basename(s)
        20 +                        ,fImgName.split("-")[1])
        21 +                    )
        22 +        #   100728 Zoom.Quiet appended <map> data
        23 +        if os.path.exists(pfImgMap):
        24 +            import re
        25 +            p=re.compile( 'title=\".+?\"')
        26 +            s += p.sub("title=\"\"",fread(pfImgMap))
        27 +            #s += fread(pfImgMap)
        28 +            #pass
        29          print '[TRACE] attachment URL:', s
        30          w(s)
        31 @@ -182,5 +196,7 @@
        32  
        33  def renderGraphImage(tool, format, imagefilename, dotfilename):
        34 -    cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" "%(dotfilename)s"' % locals()
        35 +    #100728 Zoom.Quiet fixed for export URL hotarea map export
        36 +    cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" -Tcmapx -o "%(imagefilename)s.map" "%(dotfilename)s"' % locals()
        37 +    #cmd = '%(tool)s -T%(format)s -o"%(imagefilename)s" "%(dotfilename)s"' % locals()
        38      print '[TRACE] executing:', cmd
        39      os.system(cmd)
      
  2. deploy jQuery into MoinMoin

    • path/2/moin instance/
      +-- data
          +-- plugin
              +-- theme
                  +-- u theme define .py
                  +-- woodpecker.py ~ we usage http://wiki.woodpecker.org.cn/moin/woodpecker-log/2006-10-30
              def footer(self, d, **keywords):
                  ... # appended
                  u'<!-- Finally, to loading jQuery Ajax Lib. -->',
                  u'<script src="/wiki/common/js/jquery-1.4.2.min.js" type="text/javascript"></script>',
                  u'<script src="/wiki/common/js/jquery-graphviz-map.js" type="text/javascript"></script>',
      
      +-- htdoc
          +-- common
              +-- js
                  +-- jquery-1.4.2.min.js 
                  +-- jquery-graphviz-map.js  
  3. so jquery-graphviz-map.js
    • $(document).ready(function() {
              $("img[class='attachment']").each(function(){
                  $(this).attr("usemap","#"+$(this).attr("alt"));
          });
      });

~ ZoomQuiet:

  • we need clickable mapping in MoinMoin,base Graphviz!

  • and notice,the Include() can not process html tag's attribute include Chinese!
    • such as <area title="普配主机" ...

    • moin-graphviz-erro-2010-07-30-121804_780x382_scrot.png

  • so i cleanup the title attribute content, export by dot
  • and usage {{attachment:img.png|image explain}} tell jQuery Which one image usamap Which one..

Friendly notice about syntax error

BY: BOYPT

If there's error about the graph name, committing the post will lead moin to a Uncaughted Exception page, which is very unfriendly, and dot syntax error notice will never seen by users.

Here's a patch for that. Tested under Linux, with Moin 1.9.3.

notice when fail to run dot tool

Notice when fail to run dot tool.

notice about syntax error in dot script

Notice about syntax error in dot script.

   1 --- ../MoinGraphViz-1.0rc4/wiki/data/plugin/parser/MoinGraphViz/main.py	2009-05-22 12:22:34.000000000 +0800
   2 +++ wiki/data/plugin/parser/MoinGraphViz/main.py	2010-10-27 17:10:58.236666657 +0800
   3 @@ -46,6 +46,12 @@
   4  
   5  #UID = 'BB962F5E-DB8E-424C-8E4D-D2B53286D6F3'
   6  
   7 +class GraphvizRenderError(Exception):
   8 +    pass
   9 +
  10 +class GraphvizSyntaxError(GraphvizRenderError):
  11 +    pass
  12 +
  13  class Parser:
  14      """
  15      MoinMoin GraphViz parser.
  16 @@ -62,14 +68,21 @@
  17          self.renderer = Renderer(tool, targetdir=p.getPagePath('attachments'), encoding=config.charset)
  18  
  19      def format(self, formatter):
  20 -        w = self.request.write
  21 -        ##w('<div style="border:3px ridge gray; padding:5px; width:95%; overflow:auto">')
  22 -        s = self.renderer.render(self.raw)
  23 -        fmt = moinVersion >= (1,6) and '{{attachment:%s}}' or 'attachment:%s'
  24 +        append_text = ''
  25 +        try:
  26 +            s = self.renderer.render(self.raw)
  27 +        except GraphvizSyntaxError, e:
  28 +            s = e.imagefilename
  29 +            #eliminate source path in the error message, which annoys users 
  30 +            append_text = "{{{%s}}}" % str(e).replace(e.dotfilename, "")
  31 +        except GraphvizRenderError, e:
  32 +            self.request.write("<strong class=\"error\">GraphViz: </strong><pre>%s</pre>" % e)
  33 +            return
  34 +        fmt = '{{attachment:%s}}' if moinVersion >= (1,6) else 'attachment:%s'
  35 +        fmt += append_text
  36          s = wiki2html(self.request, fmt % os.path.basename(s))
  37 -        if DEBUG: print '[TRACE] attachment URL:', s
  38 -        w(s)
  39 -        ##w('</div>')
  40 +        if DEBUG: print '[TRACE] attachment html:', s
  41 +        self.request.write(s)
  42  
  43  
  44  def parseArguments(s):
  45 @@ -105,6 +118,7 @@
  46  import sys
  47  import sha
  48  import string
  49 +from subprocess import Popen, PIPE
  50  
  51  try: __file = __file__
  52  except NameError: __file = sys.argv[0]
  53 @@ -160,6 +174,7 @@
  54          imagefilename = 'graphviz-%s-%s.%s' % (gname, uid, self.format)
  55          imagefilename = os.path.join(self.targetdir, imagefilename)
  56          ##if DEBUG: print '[TRACE] imagefilename:', imagefilename
  57 +
  58          if not os.path.isfile(imagefilename):
  59              if DEBUG: print '[TRACE] creating graph image:', imagefilename
  60              dotfilename = '%s-%s.%s' % ('graph', uid, 'graphviz.txt')
  61 @@ -167,6 +182,15 @@
  62              fwrite(dotfilename, script)
  63              try:
  64                  renderGraphImage(self.toolpath, self.format, imagefilename, dotfilename)
  65 +            except GraphvizRenderError, e:
  66 +                if os.path.exists(imagefilename):
  67 +                    #imagefilename exists means nothing terriblely happeded,
  68 +                    #just little cases about dot syntax. 
  69 +                    if DEBUG: print "[TRACE] Syntax Error"
  70 +                    e = GraphvizSyntaxError(e)
  71 +                    e.imagefilename = imagefilename
  72 +                    e.dotfilename = dotfilename
  73 +                raise e
  74              finally:
  75                  os.remove(dotfilename)
  76          return imagefilename
  77 @@ -176,20 +200,23 @@
  78          ##return string.Template(script.strip()).safe_substitute(vars(Snippets)) # for syntax $name
  79          v =  vars(Snippets)
  80          s = string.Template(script.strip() % v).safe_substitute(v) # for both syntaxes
  81 +
  82          # the following is a hint from an anonimous user, the purpose of which I do not quite
  83          # understand yet. it should fix a "unicode problem", but it is unclear what the problem
  84          # actually is and how it occurs. I'll take the patch in just in case, in the hope that
  85          # it really fixes something and someone can explain what that something is. -- ZI, 2008-04-20
  86 -        ##return unicode(s).encode(self.encoding) 
  87 -        return s
  88 +        return unicode(s).encode(self.encoding) 
  89 +        #return s
  90  
  91      def hashFor(self, content):
  92          return sha.new(content).hexdigest()
  93  
  94  def graphName(script):
  95      ##m = re.match(r'^(?:\n|\s)*(?:di)?graph\s*(\w*)', script) # allows spaces but no comments at beginning of script
  96 -    m = re.search(r'^(?:di)?graph\s*(\w*)', script, re.M)
  97 -    assert m, 'Could not derive graph name from graph script. Check the syntax, please!'
  98 +    m = re.search(r'^(?:di)?graph\s+(\w*)', script, re.M)
  99 +    if m is None:
 100 +        raise GraphvizRenderError( \
 101 +                "Could not derive graph name from graph script. Check the syntax, please!")
 102      return m.group(1)
 103  
 104  def renderGraphImage(tool, format, imagefilename, dotfilename):
 105 @@ -220,17 +247,15 @@
 106  
 107  def oscmd(cmd):
 108      '''instead of simply calling os.system(cmd)
 109 -    capture stderr and raise os.error if exit code != 0
 110 +    capture stderr and raise GraphvizRenderError if exit code != 0
 111      '''
 112 -    err = 'Unknown OS error'
 113 -    w, r, e = os.popen3(cmd)
 114 -    try:
 115 -        err = e.read()
 116 -        out = r.read()
 117 -        ##return out, err
 118 -    finally:
 119 -        for f in w, r, e: xc = f.close()
 120 -        if xc: raise os.error, err
 121 +    p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True, 
 122 +            bufsize=1024, close_fds=True)
 123 +    stdo, stde = p.communicate()
 124 +
 125 +    if p.returncode != 0:
 126 +        raise GraphvizRenderError("%s\n%s" % (stdo, stde))
 127 +
 128  
 129  ######################################################################
 130  ## 
error_notice_for_1.0rc4.patch

Encoding Problems

Fix unicode support for MoinGraphViz-1.0rc4

Fix MoinGraphViz-1.0rc4 to work with unicode, edit MoinGraphViz-1.0rc4/wiki/data/plugin/parser/MoinGraphViz/main.py, uncomment line 183. Apprently ZoranIsailovski considered about unicode.

 174     def normalizedScript(self, script=SAMPLE_SCRIPT):
 175         ##return script.strip() % vars(Snippets) # for syntax %(name)s
 176         ##return string.Template(script.strip()).safe_substitute(vars(Snippets)) # for syntax $name
 177         v =  vars(Snippets)
 178         s = string.Template(script.strip() % v).safe_substitute(v) # for both syntaxes
 179         # the following is a hint from an anonimous user, the purpose of which I do not quite
 180         # understand yet. it should fix a "unicode problem", but it is unclear what the problem
 181         # actually is and how it occurs. I'll take the patch in just in case, in the hope that
 182         # it really fixes something and someone can explain what that something is. -- ZI, 2008-04-20
 183         return unicode(s).encode(self.encoding) 
 184         return s

patches below have same effects, but this would be much more simpler.

Older Discusstions

Can anyone please have a look at this?

error, 147 print '[TRACE] creating graph image:', imagefilename, unknown encoding: cp850

http://paste.pocoo.org/show/29343/

  • I had this error only temporarily. After clearing the cache, the page dispayed correctly. -- 77.181.207.39 2008-04-15 21:08:35

    • Unfortunately, I could not reproduce that one. Do you remember the actual circumstances that led to the exception? -- ZoranIsailovski 2008-04-20 07:50:34

      • I could reproduce it, under wsgi. Commenting out the lines made it work. -- IgorTamara 2009-01-09 18:55:40


The original version works only for Windows. For unix, replace a %s/%s.exe by %s/%s in the main.py. -- 77.181.207.39 2008-04-15 21:10:31

  • I'll post a version that works with both today. :) -- ZoranIsailovski 2008-04-20 07:50:34


The original version does not work with unicode. Here is a patch

   1 --- main.py     15 Apr 2008 21:03:25 -0000      1.1
   2 +++ main.py     15 Apr 2008 21:26:37 -0000
   3 @@ -158,7 +158,7 @@
   4          ##return script.strip() % vars(Snippets) # for syntax %(name)s
   5          ##return string.Template(script.strip()).safe_substitute(vars(Snippets)) # for syntax $name
   6          v =  vars(Snippets)
   7 -        return string.Template(script.strip() % v).safe_substitute(v) # for both syntaxes
   8 +        return unicode( string.Template(script.strip() % v).safe_substitute(v) ).encode( 'utf-8' )  # for both syntaxes
   9  
  10      def hashFor(self, content):
  11          return sha.new(content).hexdigest()

A  graph [ charset="utf-8" ];  may help. -- 77.181.207.39 2008-04-15 21:28:51

  • Thx, but: I don't quite understand the problem scenario. What needs to be unicode for the parser to not work? I'd like to integrate your patch, but I'd also like to understand what it actually fixes. Please explain it in more detail. -- ZoranIsailovski 2008-04-20 07:50:34

  • Moin init the parser with a raw arg. in unicode string, if input contains non-ascii charactors, many things like sha.new(content).hexdigest() will raise an encoding exception. -- BOYPT 2024-03-19 09:53:24

1.9.2 bronk in FreeBSD

env:

  • FreeBSD 7.0-RELEASE #0: Sun Feb 24 10:35:36 UTC 2008 root@driscoll.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC amd64

  • graphviz version 2.26.3 (20100126.1600)
  • MoinGraphViz-1.0rc4

error:

  • 2010-03-25-164854_858x273_scrot.png

  • under Chinese language

fixed:

  • base 2007 old verion we fixed and can working good
  •    1 --- main.py     2009-05-22 12:22:34.000000000 +0800
       2 +++ main-xly.py 2009-02-18 21:07:43.000000000 +0800
       3 @@ -1,57 +1,52 @@
       4 
    
    • /!\ That patch does not look like just a bugfix and has some strange change, it is not minimal either. So please provide some clean changeset(s). -- ThomasWaldmann 2010-03-25 11:08:22

    • ;-) thanx ThomasWaldmann remind, i chk MoinGraphViz-1.0rc3, just fixed 2 lines ,so work!

      • now it looks like you gave a reverse patch. can you please try to give a correct one? -- ThomasWaldmann 2010-03-26 19:28:37

      •    1 --- main-1.0rc3-fixed.py        2010-03-26 23:35:37.000000000 +0800
           2 +++ main-1.0rc3.py      2008-11-20 03:58:36.000000000 +0800
           3 @@ -39,9 +39,7 @@
           4  
           5 
           6  from MoinMoin import config
           7 
           8  ##import MoinLegacy # re-inforce backward compatibility
           9 
          10 -#from umoin.MoinLegacy import wiki
          11 -from MoinMoin.parser import text_moin_wiki as wiki
          12 
          13 -
          14 
          15 +from umoin.MoinLegacy import wiki
          16 
          17  
          18 
          19  from MoinMoin.version import release as moinVersion
          20 
          21  moinVersion = tuple(map(int, moinVersion.split('.')))
          22 
          23 @@ -186,8 +184,8 @@
          24          return s
          25 
          26  
          27 
          28      def hashFor(self, content):
          29 
          30 -        return sha.new(content.encode("utf-8")).hexdigest()
          31 -        
          32 
          33 +        return sha.new(content).hexdigest()
          34 
          35 +
          36 
          37  def graphName(script):
          38 
          39      ##m = re.match(r'^(?:\n|\s)*(?:di)?graph\s*(\w*)', script) # allows spaces but no comments at beginning of script
          40 
          41      m = re.search(r'^(?:di)?graph\s*(\w*)', script, re.M)
          42 
          43 @@ -216,7 +214,7 @@
          44  def fwrite(fname, content):
          45 
          46      f = open(fname, 'wt')
          47 
          48      try:
          49 
          50 -        return f.write(str(content.encode('utf-8')))
          51 
          52 +        return f.write(str(content))
          53 
          54      finally:
          55 
          56          f.close()
        

and Chinese is support too: zoomq-2010-03-26-234934_899x399_scrot.png

Python 2.6 DeprecationWarning, patch

If you are using Python version 2.6, you get in the log file a warning: wiki/data/plugin/parser/MoinGraphViz/main.py:120: DeprecationWarning: the sha module is deprecated; use the hashlib module instead. This can be fixed by a simple patch:

about line 120
#import sha
import hashlib

about line 212
    def hashFor(self, content):
        #return sha.new(content).hexdigest()
        return hashlib.sha1(content).hexdigest()

-- RudolfReuter 2011-03-17 07:58:06


Python 2.4 patch

Patch against MoinGraphViz-1.0rc4-brn-1.9.x.comp.zip to make it work with Python 2.4

# diff -u MoinGraphViz-1.0rc4-brn-1.9.x.comp/wiki/data/plugin/parser/MoinGraphViz/main.py parser/MoinGraphViz/main.py

--- MoinGraphViz-1.0rc4-brn-1.9.x.comp/wiki/data/plugin/parser/MoinGraphViz/main.py     2010-11-18 08:48:27.000000000 -0600
+++ parser/MoinGraphViz/main.py 2011-05-05 16:10:39.000000000 -0500
@@ -79,7 +79,13 @@
         except GraphvizRenderError, e:
             self.request.write("<strong class=\"error\">GraphViz: </strong><pre>%s</pre>" % e)
             return
-        fmt = '{{attachment:%s}}' if moinVersion >= (1,6) else 'attachment:%s'
+        #fmt = '{{attachment:%s}}' if moinVersion >= (1,6) else 'attachment:%s'
+        fmt = ''
+        if moinVersion >= (1,6):
+            fmt = '{{attachment:%s}}'
+        else:
+            fmt = 'attachment:%s'
+
         fmt += append_text
         s = wiki2html(self.request, fmt % os.path.basename(s))
         if DEBUG: print '[TRACE] attachment html:', s
@@ -183,6 +189,7 @@
             fwrite(dotfilename, script)
             try:
                 renderGraphImage(self.toolpath, self.format, imagefilename, dotfilename)
+                os.remove(dotfilename)
             except GraphvizRenderError, e:
                 if os.path.exists(imagefilename):
                     #imagefilename exists means nothing terriblely happeded,
@@ -191,9 +198,10 @@
                     e = GraphvizSyntaxError(e)
                     e.imagefilename = imagefilename
                     e.dotfilename = dotfilename
+                    os.remove(dotfilename)
                 raise e
-            finally:
-                os.remove(dotfilename)
+            #finally:
+            #    os.remove(dotfilename)
         return imagefilename
 
     def normalizedScript(self, script=SAMPLE_SCRIPT):

-- Siemster 2011-05-05 21:29:49

MoinMoin: GraphVizForMoin (last edited 2012-10-19 17:06:28 by 2-227-155-252)