瀏覽代碼

Merge pull request #356 from dorneanu/dorneanu-branch

Liquid Tags: Multiline regexp, graphviz & blockdiag support
Alexis Metaireau 9 年之前
父節點
當前提交
e5c7e1f936
共有 3 個文件被更改,包括 308 次插入3 次删除
  1. 177 0
      liquid_tags/diag.py
  2. 128 0
      liquid_tags/graphviz.py
  3. 3 3
      liquid_tags/mdx_liquid_tags.py

+ 177 - 0
liquid_tags/diag.py

@@ -0,0 +1,177 @@
+"""
+Blockdiag Tag
+---------
+This tag implements a liquid style tag for blockdiag [1].  You can use different
+diagram types like blockdiag, seqdiag, packetdiag etc. [1]
+
+
+[1] http://blockdiag.com/en/blockdiag/
+
+Syntax
+------
+{% blockdiag {
+        <diagramm type> {
+            <CODE>
+        }
+    }
+%}
+
+Examples
+--------
+{% blockdiag {
+        blockdiag {
+          A -> B -> C;
+          B -> D;
+        }
+    }
+%}
+
+
+{% blockdiag {
+        actdiag {
+          A -> B -> C -> D -> E;
+
+          lane {
+            A; C; E;
+          }
+          lane {
+            B; D;
+          }
+        }
+    }
+%}
+
+
+{% blockdiag {
+        packetdiag {
+           0-7: Source Port
+           8-15: Destination Port
+           16-31: Sequence Number
+           32-47: Acknowledgment Number
+        }
+    }
+%}
+
+...
+
+
+Output
+------
+<div class="blockdiag" style="align: center;"><img src="data:image/png;base64,_BASE64_IMAGE DATA_/></div>
+
+"""
+
+import io
+import os
+import sys
+
+import base64
+import re
+from .mdx_liquid_tags import LiquidTags
+
+
+SYNTAX = '{% blockdiag [diagram type] [code] %}'
+DOT_BLOCK_RE = re.compile(r'^\s*(?P<diagram>\w+).*$', re.MULTILINE | re.DOTALL)
+
+_draw_mode = 'PNG'
+_publish_mode = 'PNG'
+
+
+def get_diag(code, command):
+    """ Generate diagramm and return data """
+    import tempfile
+    import shutil
+    code = code + u'\n'
+
+    try:
+        tmpdir = tempfile.mkdtemp()
+        fd, diag_name = tempfile.mkstemp(dir=tmpdir)
+
+        f = os.fdopen(fd, "w")
+        f.write(code.encode('utf-8'))
+        f.close()
+
+        format = _draw_mode.lower()
+        draw_name = diag_name + '.' + format
+
+        saved_argv = sys.argv
+        argv = [diag_name, '-T', format, '-o', draw_name]
+
+        if _draw_mode == 'SVG':
+            argv += ['--ignore-pil']
+
+        # Run command
+        command.main(argv)
+
+        # Read image data from file
+        file_name = diag_name + '.' + _publish_mode.lower()
+
+        with io.open(file_name, 'rb') as f:
+            data = f.read()
+            f.close()
+
+    finally:
+        for file in os.listdir(tmpdir):
+            os.unlink(tmpdir + "/" + file)
+
+        # os.rmdir will fail -> use shutil
+        shutil.rmtree(tmpdir)
+
+    return data
+
+
+def diag(code, command):
+    if command == "blockdiag":                      # blockdiag
+        import blockdiag.command
+        return get_diag(code, blockdiag.command)
+
+    elif command == "diagram":                      # diagram
+        import blockdiag.command
+        return get_diag(code, blockdiag.command)
+
+    elif command == "seqdiag":                      # seqdiag
+        import seqdiag.command
+        return get_diag(code, seqdiag.command)
+
+    elif command == "actdiag":                      # actdiag
+        import actdiag.command
+        return get_diag(code, actdiag.command)
+
+    elif command == "nwdiag":                       # nwdiag
+        import nwdiag.command
+        return get_diag(code, nwdiag.command)
+
+    elif command == "packetdiag":                   # packetdiag
+        import packetdiag.command
+        return get_diag(code, packetdiag.command)
+
+    elif command == "rackdiag":                     # racketdiag
+        import rackdiag.command
+        return get_diag(code, rackdiag.command)
+
+    else:                                           # not found
+        print("No such command %s" % command)
+        return None
+
+
+@LiquidTags.register("blockdiag")
+def blockdiag_parser(preprocessor, tag, markup):
+    """ Blockdiag parser """
+    m = DOT_BLOCK_RE.search(markup)
+    if m:
+        # Get diagram type and code
+        diagram = m.group('diagram').strip()
+        code = markup
+
+        # Run command
+        output = diag(code, diagram)
+
+        if output:
+            # Return Base64 encoded image
+            return '<div class="blockdiag" style="align: center;"><img src="data:image/png;base64,%s"></div>' % base64.b64encode(output)
+    else:
+        raise ValueError('Error processing input. '
+                         'Expected syntax: {0}'.format(SYNTAX))
+
+# This import allows image tag to be a Pelican plugin
+from .liquid_tags import register

+ 128 - 0
liquid_tags/graphviz.py

@@ -0,0 +1,128 @@
+"""
+GraphViz Tag
+---------
+This implements a Liquid-style graphviz tag for Pelican. You can use different
+Graphviz programs like dot, neato, twopi etc. [1]
+
+
+[1] http://www.graphviz.org/
+
+Syntax
+------
+{% graphviz 
+    <program> {
+        <DOT code>
+    }
+%}
+
+Examples
+--------
+{% graphviz 
+    dot {
+        digraph graphname {
+            a -> b -> c;
+            b -> d;
+        }
+    }
+%}
+
+
+{% graphviz 
+    twopi {
+        <code goes here>
+    }
+%}
+
+
+{% graphviz 
+    neato {
+        <code goes here>
+    }
+%}
+
+...
+
+
+Output
+------
+<div class="graphviz" style="align: center;"><img src="data:image/png;base64,_BASE64_IMAGE DATA_/></div>
+
+"""
+
+import base64
+import re
+from .mdx_liquid_tags import LiquidTags
+
+SYNTAX = '{% dot graphviz [program] [dot code] %}'
+DOT_BLOCK_RE = re.compile(r'^\s*(?P<program>\w+)\s*\{\s*(?P<code>.*\})\s*\}$', re.MULTILINE | re.DOTALL)
+
+
+def run_graphviz(program, code, options=[], format='png'):
+    """ Runs graphviz programs and returns image data 
+        
+        Copied from https://github.com/tkf/ipython-hierarchymagic/blob/master/hierarchymagic.py
+    """
+    import os
+    from subprocess import Popen, PIPE
+
+    dot_args = [program] + options + ['-T', format]
+
+    if os.name == 'nt':
+        # Avoid opening shell window.
+        # * https://github.com/tkf/ipython-hierarchymagic/issues/1
+        # * http://stackoverflow.com/a/2935727/727827
+        p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE, creationflags=0x08000000)
+    else:
+        p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE)
+        wentwrong = False
+
+    try:
+        # Graphviz may close standard input when an error occurs,
+        # resulting in a broken pipe on communicate()
+        stdout, stderr = p.communicate(code.encode('utf-8'))
+    except (OSError, IOError) as err:
+        if err.errno != EPIPE:
+            raise
+        wentwrong = True
+    except IOError as err:
+        if err.errno != EINVAL:
+            raise
+        wentwrong = True
+
+    if wentwrong:
+    # in this case, read the standard output and standard error streams
+    # directly, to get the error message(s)
+        stdout, stderr = p.stdout.read(), p.stderr.read()
+        p.wait()
+
+    if p.returncode != 0:
+        raise RuntimeError('dot exited with error:\n[stderr]\n{0}'.format(stderr.decode('utf-8')))
+
+    return stdout
+
+
+@LiquidTags.register('graphviz')
+def graphviz_parser(preprocessor, tag, markup):
+    """ Simple Graphviz parser """
+
+    # Parse the markup string
+    m = DOT_BLOCK_RE.search(markup)
+    if m:
+        # Get program and DOT code
+        code = m.group('code')
+        program = m.group('program').strip()
+
+        # Run specified program with our markup
+        output = run_graphviz(program, code)
+
+        # Return Base64 encoded image
+        return '<div class="graphviz" style="align: center;"><img src="data:image/png;base64,%s"></div>' % base64.b64encode(output)
+
+    else:
+        raise ValueError('Error processing input. '
+                         'Expected syntax: {0}'.format(SYNTAX))
+
+#----------------------------------------------------------------------
+# This import allows image tag to be a Pelican plugin
+from .liquid_tags import register
+

+ 3 - 3
liquid_tags/mdx_liquid_tags.py

@@ -17,7 +17,7 @@ import os
 from functools import wraps
 
 # Define some regular expressions
-LIQUID_TAG = re.compile(r'\{%.*?%\}')
+LIQUID_TAG = re.compile(r'\{%.*?%\}', re.MULTILINE | re.DOTALL)
 EXTRACT_TAG = re.compile(r'(?:\s*)(\S+)(?:\s*)')
 LT_CONFIG = { 'CODE_DIR': 'code',
               'NOTEBOOK_DIR': 'notebooks'
@@ -42,10 +42,10 @@ class _LiquidTagsPreprocessor(markdown.preprocessors.Preprocessor):
             markup = EXTRACT_TAG.sub('', markup, 1)
             if tag in self._tags:
                 liquid_tags[i] = self._tags[tag](self, tag, markup.strip())
-                
+
         # add an empty string to liquid_tags so that chaining works
         liquid_tags.append('')
- 
+
         # reconstruct string
         page = ''.join(itertools.chain(*zip(LIQUID_TAG.split(page),
                                             liquid_tags)))