Explorar el Código

Added support Markdown syntax

Added PlantUML Markdown plugin, autoregistered in the same time of the
RST plugin.
In the future the Markdown plugin may be moved in the Python Markdown
extensions with little modifications of this plugin.
mikitex70 hace 10 años
padre
commit
baee70c5ee
Se han modificado 4 ficheros con 271 adiciones y 71 borrados
  1. 77 8
      plantuml/Readme.rst
  2. 58 0
      plantuml/generateUmlDiagram.py
  3. 95 0
      plantuml/plantuml_md.py
  4. 41 63
      plantuml/plantuml_rst.py

+ 77 - 8
plantuml/Readme.rst

@@ -1,14 +1,17 @@
-PlantUML plugin for Pelican rst documents
-=========================================
+================================================
+PlantUML plugin for Pelican rst and md documents
+================================================
 
-This plugin allows you to define UML diagrams directly into rst documents using the great
+This plugin allows you to define UML diagrams directly into rst or md documents using the great
 PlantUML_ tool.
 
-This plugin gets the content of ``uml`` directive, passes it to the external
+It gets the content of ``uml`` directive, passes it to the external
 program PlantUML_ and then links the generated image to the document.
 
+.. contents::
+
 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
@@ -23,10 +26,10 @@ save te following into ``/usr/local/bin/plantuml`` (supposing PlantUML_ installe
 
 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).
+(reccomended).
 
 Usage
------
+=====
 
 Add ``plantuml`` to plugin list in ``pelicanconf.py``. For example:
 
@@ -34,6 +37,10 @@ Add ``plantuml`` to plugin list in ``pelicanconf.py``. For example:
 
     PLUGINS = [ "sitemap", "plantuml" ]
 
+One loaded the plugin register also the Pyhton-Markdown_ extension. 
+
+RST usage
+---------
 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``.
@@ -46,15 +53,50 @@ 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.
+
+MD usage
+--------
+For use with the Pyhton-Markdown_ syntax, the UML block must be enclose with ``::uml::``:
+
+.. code-block:: markdown
+
+    ::uml:: [format=...] [classes=...] [alt=...]
+       PlantUML script
+    ::end-uml::
+
+Please keep a blank line before ``::uml::`` and after ``::end-uml::`` to be sure that the UML code will be correctly
+recognized.
+See Examples_ for more details.
+
+With MD syntax options must be specified in the same line as the opening ``:uml::``, with the
+order ``format``, ``classes`` anmd ``alt``. The general syntax for option is
+
+.. code-block:: text
+
+    option="value"
+
+Option can be enclosed with single or double quotes, as you like.
+Options defaults are the same as for the rst plugin.
+
+Debugging
+---------
+The plugin can produce debugging informations to help to locate errors. To enable debugging
+execute ``pelican`` in debug mode:
+
+ .. code-block:: console
+
+     make DEBUG=1 html
+
   
 Examples
---------
+========
 
 Sequence diagram (from PlantUML_ site):
 
 .. code-block:: rst
 
   .. uml::
+    :alt: Sample sequence diagram
 
     participant User
 
@@ -76,6 +118,31 @@ Sequence diagram (from PlantUML_ site):
 Output:
 
 .. image:: http://plantuml.sourceforge.net/imgp/sequence_022.png
+   :alt: Sample sequence diagram
+
+
+Same diagram with Pyhton-Markdown_ syntax:
+
+.. code-block:: markdown
+
+    ::uml:: format="png" alt="Sample sequence diagram"
+      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
+    ::end-uml::
 
 Another example from PlantUML_ site (activity diagram):
 
@@ -119,6 +186,7 @@ Another example from PlantUML_ site (activity diagram):
 Generated image:
 
 .. image:: http://plantuml.sourceforge.net/imgp/activity2_009.png
+   :alt: Sample activity diagram
 
 
 
@@ -127,3 +195,4 @@ Generated image:
 .. _Gentoo: http://www.gentoo.org
 .. _layman: http://wiki.gentoo.org/wiki/Layman
 .. _Graphviz: http://www.graphviz.org
+.. _Pyhton-Markdown: http://pythonhosted.org/Markdown

+ 58 - 0
plantuml/generateUmlDiagram.py

@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+import logging
+import os
+import tempfile
+from zlib import adler32
+from subprocess import Popen, PIPE
+from pelican import logger
+
+
+def generate_uml_image(path, plantuml_code, imgformat):
+    tf = tempfile.NamedTemporaryFile(delete=False)
+    tf.write('@startuml\n')
+    tf.write(plantuml_code.encode('utf8'))
+    tf.write('\n@enduml')
+    tf.flush()
+
+    logger.debug("[plantuml] Temporary PlantUML source at "+(tf.name))
+
+    if imgformat == 'png':
+        imgext = ".png"
+        outopt = "-tpng"
+    elif imgformat == 'svg':
+        imgext = ".svg"
+        outopt = "-tsvg"
+    else:
+        logger.error("Bad uml image format '"+imgformat+"', using png")
+        imgext = ".png"
+        outopt = "-tpng"
+
+    # make a name
+    name = tf.name+imgext
+    # build cmd line
+    cmdline = ['plantuml', '-o', path, outopt, tf.name]
+
+    try:
+        logger.debug("[plantuml] About to execute "+" ".join(cmdline))
+        p = Popen(cmdline, stdout=PIPE, stderr=PIPE)
+        out, err = p.communicate()
+    except Exception as exc:
+        raise Exception('Failed to run plantuml: %s' % exc)
+    else:
+        if p.returncode == 0:
+            # diagram was correctly generated, we can remove the temporary file (if not debugging)
+            if not logger.isEnabledFor(logging.DEBUG):
+                os.remove(tf.name)
+            # 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(plantuml_code) & 0xffffffff))+imgext
+
+            if os.path.exists(newname):
+                os.remove(newname)
+
+            os.rename(name, newname)
+            return 'images/' + os.path.basename(newname)
+        else:
+            # the temporary file is still available as aid understanding errors
+            raise RuntimeError('Error calling plantuml: %s' % err)

+ 95 - 0
plantuml/plantuml_md.py

@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+"""
+   PlantUML_ Extension for Python-Markdown_
+   ========================================
+
+   Syntax:
+
+       ::uml:: [format="png|svg"] [classes="class1 class2 ..."] [alt="text for alt"]
+          PlantUML script diagram
+       ::end-uml::
+
+   Example:
+
+       ::uml:: format="png" classes="uml myDiagram" alt="My super diagram"
+          Goofy ->  MickeyMouse: calls
+          Goofy <-- MickeyMouse: responds
+       ::end-uml::
+
+   Options are optional, but if present must be specified in the order format, classes, alt.
+   The option value may be enclosed in single or double quotes.
+
+.. _Python-Markdown: http://pythonhosted.org/Markdown/
+.. _PlantUML: http://plantuml.sourceforge.net/
+"""
+import os
+import re
+import markdown
+from markdown.util import etree
+from generateUmlDiagram import generate_uml_image
+
+# For details see https://pythonhosted.org/Markdown/extensions/api.html#blockparser
+class PlantUMLBlockProcessor(markdown.blockprocessors.BlockProcessor):
+    # Regular expression inspired by the codehilite Markdown plugin
+    RE = re.compile(r'''::uml::
+                        \s*(format=(?P<quot>"|')(?P<format>\w+)(?P=quot))?
+                        \s*(classes=(?P<quot1>"|')(?P<classes>[\w\s]+)(?P=quot1))?
+                        \s*(alt=(?P<quot2>"|')(?P<alt>[\w\s"']+)(?P=quot2))?
+                    ''', re.VERBOSE)
+    # Regular expression for identify end of UML script
+    RE_END = re.compile(r'::end-uml::\s*$')
+
+    def test(self, parent, block):
+        return self.RE.search(block)
+
+    def run(self, parent, blocks):
+        block = blocks.pop(0)
+        text = block
+
+        # Parse configuration params
+        m = self.RE.search(block)
+        format  = m.group('format')  if m.group('format')  else self.config['format']
+        classes = m.group('classes') if m.group('classes') else self.config['classes']
+        alt     = m.group('alt')     if m.group('alt')     else self.config['alt']
+
+        # Read blocks until end marker found
+        while blocks and not self.RE_END.search(block):
+            block = blocks.pop(0)
+            text = text + '\n' + block
+        else:
+            if not blocks:
+                raise RuntimeError("[plantuml] UML block not closed, text is:\n"+text)
+
+        # Remove block header and footer
+        text = re.sub(self.RE, "", re.sub(self.RE_END, "", text))
+
+        path = os.path.abspath(os.path.join('output', 'images'))
+        if not os.path.exists(path):
+            os.makedirs(path)
+
+        # Generate image from PlantUML script
+        imageurl = self.config['siteurl']+'/'+generate_uml_image(path, text, format)
+        # Create image tag and append to the document
+        etree.SubElement(parent, "img", src=imageurl, alt=alt, classes=classes)
+
+
+# For details see https://pythonhosted.org/Markdown/extensions/api.html#extendmarkdown
+class PlantUMLMarkdownExtension(markdown.Extension):
+    # For details see https://pythonhosted.org/Markdown/extensions/api.html#configsettings
+    def __init__(self, *args, **kwargs):
+        self.config = {
+            'classes': ["uml","Space separated list of classes for the generated image. Default uml."],
+            'alt'    : ["uml diagram", "Text to show when image is not available."],
+            'format' : ["png", "Format of image to generate (png or svg). Default png."],
+            'siteurl': ["", "URL of document, used as a prefix for the image diagram."]
+        }
+
+        super(PlantUMLMarkdownExtension, self).__init__(*args, **kwargs)
+
+    def extendMarkdown(self, md, md_globals):
+        blockprocessor = PlantUMLBlockProcessor(md.parser)
+        blockprocessor.config = self.getConfigs()
+        md.parser.blockprocessors.add('plantuml', blockprocessor, '>code')
+
+def makeExtension(**kwargs):
+    return PlantUMLMarkdownExtension(**kwargs)

+ 41 - 63
plantuml/plantuml_rst.py

@@ -6,24 +6,25 @@
 .. _plantuml: http://plantuml.sourceforge.net/
 """
 
+import sys
 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 signals, logger
 
-from pelican import logger, signals
+from generateUmlDiagram import generate_uml_image
 
-global_siteurl = ""
 
-class PlantUML(Directive):
+global_siteurl = "" # URL of the site, filled on plugin initialization
+
+
+class PlantUML_rst(Directive):
+    """ reST directive for PlantUML """
     required_arguments = 0
     optional_arguments = 0
     has_content = True
-    
+
     global global_siteurl
 
     option_spec = {
@@ -33,80 +34,57 @@ class PlantUML(Directive):
     }
 
     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:
+            url = global_siteurl+'/'+generate_uml_image(path, body, "png")
+        except Exception as exc:
             error = self.state_machine.reporter.error(
-                'Failed to run plantuml: %s' % (exc, ),
+                '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)
+            alt = self.options.get('alt', 'uml diagram')
+            classes = self.options.pop('class', ['uml'])
+            imgnode = image(uri=url, classes=classes, alt=alt)
+            nodes.append(imgnode)
 
         return nodes
 
+
 def custom_url(generator, metadata):
+    """ Saves globally the value of SITEURL configuration parameter """
     global global_siteurl
     global_siteurl = generator.settings['SITEURL']
-    
+
+
+def pelican_init(pelicanobj):
+    """ Prepare configurations for the MD plugin """
+    try:
+        import markdown
+        from plantuml_md import PlantUMLMarkdownExtension
+    except:
+        # Markdown not available
+        logger.debug("[plantuml] Markdown support not available")
+        return
+
+    # Register the Markdown plugin
+    config = { 'siteurl': pelicanobj.settings['SITEURL'] }
+
+    try:
+        pelicanobj.settings['MD_EXTENSIONS'].append(PlantUMLMarkdownExtension(config))
+    except:
+        logger.error("[plantuml] Unable to configure plantuml markdown extension")
+
+
 def register():
     """Plugin registration."""
+    signals.initialized.connect(pelican_init)
     signals.article_generator_context.connect(custom_url)
-    directives.register_directive('uml', PlantUML)
+    directives.register_directive('uml', PlantUML_rst)