chiark / gitweb /
m.dot: extract the reusable guts to an independent module.
authorVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Oct 2018 00:03:49 +0000 (02:03 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Sun, 14 Oct 2018 18:17:49 +0000 (20:17 +0200)
Will get used in the Doxygen theme, where we don't need docutils or
Pelican.

pelican-plugins/dot2svg.py [new file with mode: 0644]
pelican-plugins/m/dot.py

diff --git a/pelican-plugins/dot2svg.py b/pelican-plugins/dot2svg.py
new file mode 100644 (file)
index 0000000..9ff7811
--- /dev/null
@@ -0,0 +1,121 @@
+#
+#   This file is part of m.css.
+#
+#   Copyright © 2017, 2018 Vladimír Vondruš <mosra@centrum.cz>
+#
+#   Permission is hereby granted, free of charge, to any person obtaining a
+#   copy of this software and associated documentation files (the "Software"),
+#   to deal in the Software without restriction, including without limitation
+#   the rights to use, copy, modify, merge, publish, distribute, sublicense,
+#   and/or sell copies of the Software, and to permit persons to whom the
+#   Software is furnished to do so, subject to the following conditions:
+#
+#   The above copyright notice and this permission notice shall be included
+#   in all copies or substantial portions of the Software.
+#
+#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+#   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+#   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+#   DEALINGS IN THE SOFTWARE.
+#
+
+import re
+import subprocess
+
+_patch_src = re.compile(r"""<\?xml version="1\.0" encoding="UTF-8" standalone="no"\?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1\.1//EN"
+ "http://www\.w3\.org/Graphics/SVG/1\.1/DTD/svg11\.dtd">
+<svg width="(?P<width>\d+)pt" height="(?P<height>\d+)pt"
+ viewBox="(?P<viewBox>[^"]+)" xmlns="http://www\.w3\.org/2000/svg" xmlns:xlink="http://www\.w3\.org/1999/xlink">
+<g id="graph0" class="graph" """)
+
+_patch_dst = r"""<svg style="width: {width:.3f}rem; height: {height:.3f}rem;" viewBox="{viewBox}">
+<g """
+
+_comment_src = re.compile(r"""<!--[^-]+-->\n""")
+
+# Graphviz < 2.40 (Ubuntu 16.04 and older) doesn't have a linebreak between <g>
+# and <title>
+_class_src = re.compile(r"""<g id="(edge|node)\d+" class="(?P<type>edge|node)(?P<classes>[^"]*)">[\n]?<title>(?P<title>[^<]*)</title>
+<(?P<element>ellipse|polygon|path) fill="(?P<fill>[^"]+)" stroke="[^"]+" """)
+
+_class_dst = r"""<g class="{classes}">
+<title>{title}</title>
+<{element} """
+
+_attributes_src = re.compile(r"""<(?P<element>ellipse|polygon|polyline) fill="[^"]+" stroke="[^"]+" """)
+
+_attributes_dst = r"""<\g<element> """
+
+# re.compile() is called after replacing {font} in configure(). Graphviz < 2.40
+# doesn't put the fill="" attribute there
+_text_src_src = ' font-family="{font}" font-size="(?P<size>[^"]+)"( fill="[^"]+")?'
+
+_text_dst = ' style="font-size: {size}px;"'
+
+_font = ''
+_font_size = 0.0
+
+# The pt are actually px (16pt font is the same size as 16px), so just
+# converting to rem here
+def _pt2em(pt): return pt/_font_size
+
+def dot2svg(source):
+    try:
+        ret = subprocess.run(['dot', '-Tsvg',
+            '-Gfontname={}'.format(_font),
+            '-Nfontname={}'.format(_font),
+            '-Efontname={}'.format(_font),
+            '-Gfontsize={}'.format(_font_size),
+            '-Nfontsize={}'.format(_font_size),
+            '-Efontsize={}'.format(_font_size),
+            '-Gbgcolor=transparent',
+            ], input=source.encode('utf-8'), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        if ret.returncode: print(ret.stderr.decode('utf-8'))
+        ret.check_returncode()
+    except FileNotFoundError: # pragma: no cover
+        raise RuntimeError("dot not found")
+
+    # First remove comments
+    svg = _comment_src.sub('', ret.stdout.decode('utf-8'))
+
+    # Remove preamble and fixed size
+    def patch_repl(match): return _patch_dst.format(
+        width=_pt2em(float(match.group('width'))),
+        height=_pt2em(float(match.group('height'))),
+        viewBox=match.group('viewBox'))
+    svg = _patch_src.sub(patch_repl, svg)
+
+    # Remove unnecessary IDs and attributes, replace classes for elements
+    def element_repl(match):
+        classes = ['m-' + match.group('type')] + match.group('classes').replace('&#45;', '-').split()
+        # distinguish between solid and filled nodes
+        if match.group('type') == 'node' and match.group('fill') == 'none':
+            classes += ['m-flat']
+
+        return _class_dst.format(
+            classes=' '.join(classes),
+            title=match.group('title'),
+            element=match.group('element'))
+    svg = _class_src.sub(element_repl, svg)
+
+    # Remove unnecessary fill and stroke attributes
+    svg = _attributes_src.sub(_attributes_dst, svg)
+
+    # Remove unnecessary text attributes. Keep font size only if nondefault
+    def text_repl(match):
+        if float(match.group('size')) != _font_size:
+            return _text_dst.format(size=float(match.group('size')))
+        return ''
+    svg = _text_src.sub(text_repl, svg)
+
+    return svg
+
+def configure(font, font_size):
+    global _font, _font_size, _text_src
+    _font = font
+    _font_size = font_size
+    _text_src = re.compile(_text_src_src.format(font=_font))
index 164ff29b70c9713ba23abeb29b6ce9cf3f87a6fc..04e2a41eeba490ca940543efd9ad2943819f55de 100644 (file)
@@ -31,43 +31,7 @@ from docutils.parsers import rst
 from docutils.parsers.rst import directives
 from docutils.parsers.rst.roles import set_classes
 
-_patch_src = re.compile(r"""<\?xml version="1\.0" encoding="UTF-8" standalone="no"\?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1\.1//EN"
- "http://www\.w3\.org/Graphics/SVG/1\.1/DTD/svg11\.dtd">
-<svg width="(?P<width>\d+)pt" height="(?P<height>\d+)pt"
- viewBox="(?P<viewBox>[^"]+)" xmlns="http://www\.w3\.org/2000/svg" xmlns:xlink="http://www\.w3\.org/1999/xlink">
-<g id="graph0" class="graph" """)
-
-_patch_dst = r"""<svg style="width: {width:.3f}rem; height: {height:.3f}rem;" viewBox="{viewBox}">
-<g """
-
-_comment_src = re.compile(r"""<!--[^-]+-->\n""")
-
-# Graphviz < 2.40 (Ubuntu 16.04 and older) doesn't have a linebreak between <g>
-# and <title>
-_class_src = re.compile(r"""<g id="(edge|node)\d+" class="(?P<type>edge|node)(?P<classes>[^"]*)">[\n]?<title>(?P<title>[^<]*)</title>
-<(?P<element>ellipse|polygon|path) fill="(?P<fill>[^"]+)" stroke="[^"]+" """)
-
-_class_dst = r"""<g class="{classes}">
-<title>{title}</title>
-<{element} """
-
-_attributes_src = re.compile(r"""<(?P<element>ellipse|polygon|polyline) fill="[^"]+" stroke="[^"]+" """)
-
-_attributes_dst = r"""<\g<element> """
-
-# re.compile() is called after replacing {font} in configure(). Graphviz < 2.40
-# doesn't put the fill="" attribute there
-_text_src_src = ' font-family="{font}" font-size="(?P<size>[^"]+)"( fill="[^"]+")?'
-
-_text_dst = ' style="font-size: {size}px;"'
-
-_font = ''
-_font_size = 0.0
-
-# The pt are actually px (16pt font is the same size as 16px), so just
-# converting to rem here
-def _pt2em(pt): return pt/_font_size
+import dot2svg
 
 class Dot(rst.Directive):
     has_content = True
@@ -79,55 +43,7 @@ class Dot(rst.Directive):
     def run(self, source):
         set_classes(self.options)
 
-        title_text = self.arguments[0]
-
-        try:
-            ret = subprocess.run(['dot', '-Tsvg',
-                '-Gfontname={}'.format(_font),
-                '-Nfontname={}'.format(_font),
-                '-Efontname={}'.format(_font),
-                '-Gfontsize={}'.format(_font_size),
-                '-Nfontsize={}'.format(_font_size),
-                '-Efontsize={}'.format(_font_size),
-                '-Gbgcolor=transparent',
-                ], input=source.encode('utf-8'), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-            if ret.returncode: print(ret.stderr.decode('utf-8'))
-            ret.check_returncode()
-        except FileNotFoundError: # pragma: no cover
-            raise RuntimeError("dot not found")
-
-        # First Remove comments
-        svg = _comment_src.sub('', ret.stdout.decode('utf-8'))
-
-        # Remove preamble and fixed size
-        def patch_repl(match): return _patch_dst.format(
-            width=_pt2em(float(match.group('width'))),
-            height=_pt2em(float(match.group('height'))),
-            viewBox=match.group('viewBox'))
-        svg = _patch_src.sub(patch_repl, svg)
-
-        # Remove unnecessary IDs and attributes, replace classes for elements
-        def element_repl(match):
-            classes = ['m-' + match.group('type')] + match.group('classes').replace('&#45;', '-').split()
-            # distinguish between solid and filled nodes
-            if match.group('type') == 'node' and match.group('fill') == 'none':
-                classes += ['m-flat']
-
-            return _class_dst.format(
-                classes=' '.join(classes),
-                title=match.group('title'),
-                element=match.group('element'))
-        svg = _class_src.sub(element_repl, svg)
-
-        # Remove unnecessary fill and stroke attributes
-        svg = _attributes_src.sub(_attributes_dst, svg)
-
-        # Remove unnecessary text attributes. Keep font size only if nondefault
-        def text_repl(match):
-            if float(match.group('size')) != _font_size:
-                return _text_dst.format(size=float(match.group('size')))
-            return ''
-        svg = _text_src.sub(text_repl, svg)
+        svg = dot2svg.dot2svg(source)
 
         container = nodes.container(**self.options)
         container['classes'] = ['m-graph'] + container['classes']
@@ -152,10 +68,9 @@ class StrictGraph(Dot):
         return Dot.run(self, 'strict graph "{}" {{\n{}}}'.format(self.arguments[0], '\n'.join(self.content)))
 
 def configure(pelicanobj):
-    global _font, _font_size, _text_src
-    _font = pelicanobj.settings.get('M_DOT_FONT', 'Source Sans Pro')
-    _font_size = pelicanobj.settings.get('M_DOT_FONT_SIZE', 16.0)
-    _text_src = re.compile(_text_src_src.format(font=_font))
+    dot2svg.configure(
+        pelicanobj.settings.get('M_DOT_FONT', 'Source Sans Pro'),
+        pelicanobj.settings.get('M_DOT_FONT_SIZE', 16.0))
 
 def register():
     pelican.signals.initialized.connect(configure)