浏览代码

Merge pull request #214 from mikitex70/master

Add plantuml_rst plugin
Justin Mayer 10 年之前
父节点
当前提交
da233a76d0
共有 3 个文件被更改,包括 242 次插入0 次删除
  1. 129 0
      plantuml/Readme.rst
  2. 1 0
      plantuml/__init__.py
  3. 112 0
      plantuml/plantuml_rst.py

+ 129 - 0
plantuml/Readme.rst

@@ -0,0 +1,129 @@
+PlantUML plugin for Pelican rst documents
+=========================================
+
+This plugin allows you to define UML diagrams directly into rst documents using the great
+PlantUML_ tool.
+
+This plugin gets the content of ``uml`` directive, passes it to the external
+program PlantUML_ and then links the generated image to the document.
+
+Installation
+------------
+
+You need to install PlantUML_ (see the site for details) and Graphviz_ 2.26.3 or later.
+The plugin expects a program ``plantuml`` in the classpath. If not installed by your package
+manager, you can create a shell script and place it somewhere in the classpath. For example,
+save te following into ``/usr/local/bin/plantuml`` (supposing PlantUML_ installed into
+``/opt/plantuml``):
+
+.. code-block:: bash
+
+    #!/bin/bash
+    java -jar /opt/plantuml/plantuml.jar ${@}
+
+For Gentoo_ there is an ebuild at http://gpo.zugaina.org/dev-util/plantuml/RDep: you can download
+the ebuild and the ``files`` subfolder or you can add the ``zugaina`` repository with _layman
+(raccomended).
+
+Usage
+-----
+
+Add ``plantuml`` to plugin list in ``pelicanconf.py``. For example:
+
+.. code-block:: ptyhon
+
+    PLUGINS = [ "sitemap", "plantuml" ]
+
+Use the ``uml`` directive to start UML diagram description. It is not necessary to enclose
+diagram body between ``@startuml`` and ``@enduml`` directives: they are added automatically 
+before calling ``plantuml``.
+
+In addition to ``class`` and
+``alt`` options common to all images, you can use the ``format`` option to select what kind
+of image must be produced. At the moment only ``png`` and ``svg`` are supported; the default
+is ``png``.
+
+Please note that the ``format`` option in not recognized by the ``plantuml`` extension of
+``rst2pdf`` utility (call it with ``-e plantuml.py``) so if you use it you can get errors from
+that program.
+  
+Examples
+--------
+
+Sequence diagram (from PlantUML_ site):
+
+.. code-block:: rst
+
+  .. uml::
+
+    participant User
+
+    User -> A: DoWork
+    activate A #FFBBBB
+
+    A -> A: Internal call
+    activate A #DarkSalmon
+
+    A -> B: << createRequest >>
+    activate B
+
+    B --> A: RequestCreated
+    deactivate B
+    deactivate A
+    A -> User: Done
+    deactivate A
+
+Output:
+
+.. image:: http://plantuml.sourceforge.net/imgp/sequence_022.png
+
+Another example from PlantUML_ site (activity diagram):
+
+.. code-block:: rst
+
+  .. uml::
+
+    start
+    :ClickServlet.handleRequest();
+    :new page;
+    if (Page.onSecurityCheck) then (true)
+      :Page.onInit();
+      if (isForward?) then (no)
+	:Process controls;
+	if (continue processing?) then (no)
+	  stop
+	endif
+	
+	if (isPost?) then (yes)
+	  :Page.onPost();
+	else (no)
+	  :Page.onGet();
+	endif
+	:Page.onRender();
+      endif
+    else (false)
+    endif
+
+    if (do redirect?) then (yes)
+      :redirect process;
+    else
+      if (do forward?) then (yes)
+	:Forward request;
+      else (no)
+	:Render page template;
+      endif
+    endif
+
+    stop
+
+Generated image:
+
+.. image:: http://plantuml.sourceforge.net/imgp/activity2_009.png
+
+
+
+.. _PlantUML: http://plantuml.sourceforge.net
+.. _Sabayon: http://www.sabayon.org
+.. _Gentoo: http://www.gentoo.org
+.. _layman: http://wiki.gentoo.org/wiki/Layman
+.. _Graphviz: http://www.graphviz.org

+ 1 - 0
plantuml/__init__.py

@@ -0,0 +1 @@
+from plantuml_rst import *

+ 112 - 0
plantuml/plantuml_rst.py

@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+"""Custom reST_ directive for plantuml_ integration.
+   Adapted from ditaa_rst plugin.
+
+.. _reST: http://docutils.sourceforge.net/rst.html
+.. _plantuml: http://plantuml.sourceforge.net/
+"""
+
+import os
+import tempfile
+from zlib import adler32
+from subprocess import Popen, PIPE
+
+from docutils.nodes import image, literal_block
+from docutils.parsers.rst import Directive, directives
+from docutils import utils, nodes
+
+from pelican import logger, signals
+
+global_siteurl = ""
+
+class PlantUML(Directive):
+    required_arguments = 0
+    optional_arguments = 0
+    has_content = True
+    
+    global global_siteurl
+
+    option_spec = {
+        'class' : directives.class_option,
+        'alt'   : directives.unchanged,
+        'format': directives.unchanged,
+    }
+
+    def run(self):
+	source = self.state_machine.input_lines.source(self.lineno - self.state_machine.input_offset - 1)
+	source_dir = os.path.dirname(os.path.abspath(source))
+	source_dir = utils.relative_path(None, source_dir)
+
+        path = os.path.abspath(os.path.join('output', 'images'))
+        if not os.path.exists(path):
+            os.makedirs(path)
+
+        nodes = []
+
+        body = '\n'.join(self.content)
+        tf = tempfile.NamedTemporaryFile(delete=True)
+        tf.write('@startuml\n')
+        tf.write(body.encode('utf8'))
+        tf.write('\n@enduml')
+        tf.flush()
+        
+        imgformat = self.options.get('format', 'png')
+        
+        if imgformat == 'png':
+            imgext = ".png"
+            outopt = "-tpng"
+        elif imgformat == 'svg':
+            imgext = ".svg"
+            outopt = "-tsvg"
+        else:
+	    logger.error("Bad uml image format: "+imgformat)
+
+        # make a name
+        name =  tf.name+imgext
+
+        alt = self.options.get('alt', 'uml diagram')
+        classes = self.options.pop('class', ['uml'])
+        cmdline = ['plantuml', '-o', path, outopt, tf.name ]
+
+        try:
+            p = Popen(cmdline, stdout=PIPE, stderr=PIPE)
+            out, err = p.communicate()
+        except Exception, exc:
+            error = self.state_machine.reporter.error(
+                'Failed to run plantuml: %s' % (exc, ),
+                literal_block(self.block_text, self.block_text),
+                line=self.lineno)
+            nodes.append(error)
+        else:
+            if p.returncode == 0:
+	        # renaming output image using an hash code, just to not pullate 
+	        # output directory with a growing number of images
+                name = os.path.join(path, os.path.basename(name))
+	        newname = os.path.join(path, "%08x" % (adler32(body) & 0xffffffff))+imgext
+	        
+	        try: # for Windows
+		    os.remove(newname)  
+		except Exception, exc:
+		    logger.debug('File '+newname+' does not exist, not deleted')
+		
+	        os.rename(name, newname)
+                url = global_siteurl + '/images/' + os.path.basename(newname)
+                imgnode = image(uri=url, classes=classes, alt=alt)
+                nodes.append(imgnode)
+            else:
+                error = self.state_machine.reporter.error(
+                    'Error in "%s" directive: %s' % (self.name, err),
+                    literal_block(self.block_text, self.block_text),
+                    line=self.lineno)
+                nodes.append(error)
+
+        return nodes
+
+def custom_url(generator, metadata):
+    global global_siteurl
+    global_siteurl = generator.settings['SITEURL']
+    
+def register():
+    """Plugin registration."""
+    signals.article_generator_context.connect(custom_url)
+    directives.register_directive('uml', PlantUML)