chiark / gitweb /
documentation/python: support plugins.
authorVladimír Vondruš <mosra@centrum.cz>
Sun, 5 May 2019 14:05:10 +0000 (16:05 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Tue, 21 May 2019 14:51:51 +0000 (16:51 +0200)
It looks like we're 90% there, hah.

31 files changed:
doc/documentation/python.rst
doc/plugins.rst
doc/plugins/components.rst
doc/plugins/htmlsanity.rst
doc/plugins/images.rst
doc/plugins/links.rst
doc/plugins/math-and-code.rst
doc/plugins/plots-and-graphs.rst
documentation/python.py
documentation/test_python/page_plugins/dot-238.html [new file with mode: 0644]
documentation/test_python/page_plugins/dot.html [new file with mode: 0644]
documentation/test_python/page_plugins/dot.rst [new file with mode: 0644]
documentation/test_python/page_plugins/index.html [new file with mode: 0644]
documentation/test_python/page_plugins/index.rst [new file with mode: 0644]
documentation/test_python/page_plugins/plugins/fancyline.py [new file with mode: 0644]
documentation/test_python/test_page.py
plugins/m/abbr.py
plugins/m/alias.py
plugins/m/code.py
plugins/m/components.py
plugins/m/dot.py
plugins/m/dox.py
plugins/m/filesize.py
plugins/m/gh.py
plugins/m/gl.py
plugins/m/images.py
plugins/m/link.py
plugins/m/math.py
plugins/m/metadata.py
plugins/m/plots.py
plugins/m/vk.py

index 00061f93a1c4178f94d5b3eb14340d3993bfd088..ffe9d39bac0cbf1a2aa5f3a401b448811f4f7f8b 100644 (file)
@@ -200,6 +200,11 @@ Variable                            Description
 :py:`FORMATTED_METADATA: List[str]` Which meatadata fields should be formatted
                                     in documentation pages. By default only
                                     the ``summary`` field is.
+:py:`PLUGINS: List[str]`            List of `plugins <{filename}/plugins.rst>`_
+                                    to use. See `Plugins`_ for more
+                                    information.
+:py:`PLUGIN_PATHS: List[str]`       Additional plugin search paths. Relative
+                                    paths are relative to :py:`INPUT`.
 :py:`CLASS_INDEX_EXPAND_LEVELS`     How many levels of the class index tree to
                                     expand. :py:`0` means only the top-level
                                     symbols are shown. If not set, :py:`1` is
@@ -569,6 +574,54 @@ listed in :py:`FORMATTED_METADATA` (the :py:`:summary:` is among them) are
 expected to be formatted as :abbr:`reST <reStructuredText>` and exposed as
 HTML, otherwise as a plain text.
 
+`Plugins`_
+==========
+
+The :abbr:`reST <reStructuredText>` content is not limited to just the builtin
+functionality and it's possible to extend it via plugins eiter
+`from m.css itself <{filename}/plugins.rst>`_ or 3rd party ones. See
+documentation of each plugin to see its usage; the
+`m.htmlsanity <{filename}/plugins/htmlsanity.rst>`_ plugin is used
+unconditionally while all others are optional. For example, enabling the common
+m.css plugins might look like this:
+
+.. code:: py
+
+    PLUGINS = ['m.code', 'm.components', 'm.dox']
+
+`Implementing custom plugins`_
+------------------------------
+
+Other plugins can be loaded from paths specified in :py:`PLUGIN_PATHS`. Custom
+plugins need to implement a registration function named :py:`register_mcss()`.
+It gets passed the following named arguments and the plugin might or might not
+use them.
+
+.. class:: m-table
+
+=========================== ===================================================
+Keyword argument            Content
+=========================== ===================================================
+:py:`mcss_settings`         Dict containing all m.css settings
+:py:`jinja_environment`     Jinja2 environment. Useful for adding new filters
+                            etc.
+=========================== ===================================================
+
+Registration function for a plugin that needs to query the :py:`OUTPUT` setting
+might look like this --- the remaining keyword arguments will collapse into
+the :py:`**kwargs` parameter. See code of various m.css plugins for actual
+examples.
+
+.. code:: py
+
+    output_dir = None
+
+    …
+
+    def register_mcss(mcss_settings, **kwargs):
+        global output_dir
+        output_dir = mcss_settings['OUTPUT']
+
 `pybind11 compatibility`_
 =========================
 
index ddf585f2772c71cb3a3c601939934434e7bd3354..b258d04d4dcd89f8e9911176ef74c21172c64d19 100644 (file)
@@ -61,9 +61,10 @@ below or :gh:`grab the whole Git repository <mosra/m.css>`:
     :gh:`m.alias <mosra/m.css$master/plugins/m/alias.py>`
 -   :gh:`m.metadata <mosra/m.css$master/plugins/m/metadata.py>`
 
-All plugins that make sense in the context of the
-`Doxygen theme <{filename}/documentation/doxygen.rst>`_ are implicitly exposed
-to it, without needing to explicitly enable them.
+For the `Python doc theme <{filename}/documentation/python.rst>`_ it's enough
+to simply list them in :py:`PLUGINS`. For the `Doxygen theme <{filename}/documentation/doxygen.rst>`_,
+all plugins that make sense in its context are implicitly exposed to it,
+without needing to explicitly enable them.
 
 Note that particular plugins can have additional dependencies, see
 documentation of each of them to see more. Click on the headings below to get
index c74150b831a4c551cdc738a9b1eabbe4546cce94..f3ae7bca37165f8683fe50a6403c14742bbc95b8 100644 (file)
@@ -62,6 +62,16 @@ plugin assumes presence of `m.htmlsanity <{filename}/plugins/htmlsanity.rst>`_.
 
     PLUGINS += ['m.htmlsanity', 'm.components']
 
+`Python doc theme`_
+-------------------
+
+Simply list the plugin in your :py:`PLUGINS`. The `m.htmlsanity`_ plugin is
+available always, no need to mention it explicitly:
+
+.. code:: py
+
+    PLUGINS += ['m.components']
+
 `Doxygen theme`_
 ----------------
 
index a3367ea5509ea29cf3448653049823cf544e1642..39291ea4a6f03dd59e2b931cd30dd59cb4a17c09 100644 (file)
@@ -71,6 +71,18 @@ it with the above setting.
 
     pip3 install Pyphen
 
+`Python doc theme`_
+-------------------
+
+The ``m.htmlsanity`` plugin is available always, no need to mention it
+explicitly. However, the options aren't, so you might want to supply them.
+The same dependencies as for `Pelican`_ apply here.
+
+.. code:: py
+
+    M_HTMLSANITY_SMART_QUOTES = True
+    M_HTMLSANITY_HYPHENATION = True
+
 `Doxygen theme`_
 ----------------
 
index 3ced8805f38f9900cc3a4a83b5abdc59f15a0fe6..c20a17822498bafec860b056f2be0c7c80a28ace 100644 (file)
@@ -69,6 +69,18 @@ library installed. Get it via ``pip`` or your distribution package manager:
 
     pip3 install Pillow
 
+`Python doc theme`_
+-------------------
+
+Simply list the plugin in your :py:`PLUGINS`. The `m.htmlsanity`_ plugin is
+available always, no need to mention it explicitly. The same dependencies as
+for `Pelican`_ apply here.
+
+.. code:: py
+
+    PLUGINS += ['m.images']
+    M_IMAGES_REQUIRE_ALT_TEXT = False
+
 `Doxygen theme`_
 ----------------
 
index 6e334b54051a1d0149622df2b3017c9146243c75..035e0de1a5819c823eb6ca8e9b17ad1dedde44d1 100644 (file)
@@ -66,7 +66,8 @@ own requirements.
 
 For Pelican, download the `m/link.py <{filename}/plugins.rst>`_ file, put it
 including the ``m/`` directory into one of your :py:`PLUGIN_PATHS` and add
-:py:`m.link` package to your :py:`PLUGINS` in ``pelicanconf.py``:
+:py:`m.link` package to your :py:`PLUGINS` in ``pelicanconf.py``. For the
+Python doc theme, it's enough to just list it in :py:`PLUGINS`:
 
 .. code:: python
 
@@ -95,7 +96,8 @@ additional CSS classes. At the moment the plugin knows only external URLs.
 
 For Pelican, download the `m/gh.py <{filename}/plugins.rst>`_ file, put it
 including the ``m/`` directory into one of your :py:`PLUGIN_PATHS` and add
-:py:`m.gh` package to your :py:`PLUGINS` in ``pelicanconf.py``:
+:py:`m.gh` package to your :py:`PLUGINS` in ``pelicanconf.py``. For the Python
+doc theme, it's enough to just list it in :py:`PLUGINS`:
 
 .. code:: python
 
@@ -152,7 +154,8 @@ CSS classes by deriving the role and adding the :rst:`:class:` option.
 
 For Pelican, download the `m/gl.py <{filename}/plugins.rst>`_ file, put it
 including the ``m/`` directory into one of your :py:`PLUGIN_PATHS` and add
-:py:`m.gl` package to your :py:`PLUGINS` in ``pelicanconf.py``:
+:py:`m.gl` package to your :py:`PLUGINS` in ``pelicanconf.py``. For the Python
+doc theme, it's enough to just list it in :py:`PLUGINS`:
 
 .. code:: python
 
@@ -200,7 +203,8 @@ and adding the :rst:`:class:` option.
 
 For Pelican, download the `m/vk.py <{filename}/plugins.rst>`_ file, put it
 including the ``m/`` directory into one of your :py:`PLUGIN_PATHS` and add
-:py:`m.vk` package to your :py:`PLUGINS` in ``pelicanconf.py``:
+:py:`m.vk` package to your :py:`PLUGINS` in ``pelicanconf.py``. For the Python
+doc theme, it's enough to just list it in :py:`PLUGINS`:
 
 .. code:: python
 
@@ -259,6 +263,9 @@ the plugin work. Example configuration:
         ('doxygen/corrade.tag', 'https://doc.magnum.graphics/corrade/', ['Corrade::']),
         ('doxygen/magnum.tag', 'https://doc.magnum.graphics/magnum/', ['Magnum::'])]
 
+For the Python doc theme, the configuration is the same. Tag file paths are
+relative to the configuration file location or to :py:`PATH`, if specified.
+
 Use the :rst:`:dox:` interpreted text role for linking to documented symbols.
 All link targets understood by Doxygen's ``@ref`` or ``@link`` commands are
 understood by this plugin as well, in addition it's possible to link to the
index 01497aacacdfc4af2203e784ab694c37b68c726c..e00dfadbd489821d4de9c3692b5c127e21f9e33a 100644 (file)
@@ -71,6 +71,13 @@ files, put them including the ``m/`` directory into one of your
     M_MATH_RENDER_AS_CODE = False
     M_MATH_CACHE_FILE = 'm.math.cache'
 
+For the Python doc theme, it's enough to mention it in :py:`PLUGINS`. The
+`m.htmlsanity`_ plugin is available always, no need to mention it explicitly:
+
+.. code:: py
+
+    PLUGINS += ['m.code']
+
 For the Doxygen theme, this feature is builtin. Use either the ``@f[`` command
 for block-level math or the ``@f$`` command for inline math. It's possible to
 add extra CSS classes by placing ``@m_class`` in a paragraph before the actual
@@ -290,6 +297,13 @@ plugin assumes presence of `m.htmlsanity <{filename}/plugins/htmlsanity.rst>`_.
 
     PLUGINS += ['m-htmlsanity', 'm.code']
 
+For the Python doc theme, it's enough to mention it in :py:`PLUGINS`. The
+`m.htmlsanity`_ plugin is available always, no need to mention it explicitly:
+
+.. code:: py
+
+    PLUGINS += ['m.code']
+
 For the Doxygen theme, this feature is builtin. Use the ``@code{.ext}`` command
 either in a block or inline, the various ``@include`` and ``@snippet`` commands
 support it as well. Language detection is done from the value of ``.ext`` in
index 43f6ba234e4089231416b95e4f8e3511100a52d5..de90904244785d8feea0bc6987905d408ff999a6 100644 (file)
@@ -61,7 +61,8 @@ the graphics is rendered to a SVG that's embedded directly in the HTML markup.
 
 For Pelican, download the `m/plots.py <{filename}/plugins.rst>`_ file, put it
 including the ``m/`` directory into one of your :py:`PLUGIN_PATHS` and add
-``m.plots`` package to your :py:`PLUGINS` in ``pelicanconf.py``.
+``m.plots`` package to your :py:`PLUGINS` in ``pelicanconf.py``. For the
+Python doc theme, it's enough to just list it in :py:`PLUGINS`:
 
 .. code:: python
 
index eb88ab249205f9f98fad68e8d12706c1287d5e64..15c3131a9271f46aadd40dce423bef80bbcb4bfd 100755 (executable)
@@ -76,6 +76,9 @@ default_config = {
     'FINE_PRINT': '[default]',
     'FORMATTED_METADATA': ['summary'],
 
+    'PLUGINS': [],
+    'PLUGIN_PATHS': [],
+
     'CLASS_INDEX_EXPAND_LEVELS': 1,
     'CLASS_INDEX_EXPAND_INNER': False,
 
@@ -922,9 +925,6 @@ def run(basedir, config, templates):
     env.filters['path_to_url'] = path_to_url
     env.filters['urljoin'] = urljoin
 
-    # Set up the plugins
-    m.htmlsanity.register_mcss(config, env)
-
     # Populate the INPUT, if not specified, make it absolute
     if config['INPUT'] is None: config['INPUT'] = basedir
     else: config['INPUT'] = os.path.join(basedir, config['INPUT'])
@@ -939,6 +939,15 @@ def run(basedir, config, templates):
 
     state = State(config)
 
+    # Set up extra plugin paths. The one for m.css plugins was added above.
+    for path in config['PLUGIN_PATHS']:
+        if path not in sys.path: sys.path.append(os.path.join(config['INPUT'], path))
+
+    # Import plugins
+    for plugin in ['m.htmlsanity'] + config['PLUGINS']:
+        module = importlib.import_module(plugin)
+        module.register_mcss(mcss_settings=config, jinja_environment=env)
+
     for module in config['INPUT_MODULES']:
         if isinstance(module, str):
             module_name = module
diff --git a/documentation/test_python/page_plugins/dot-238.html b/documentation/test_python/page_plugins/dot-238.html
new file mode 100644 (file)
index 0000000..6caced7
--- /dev/null
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Dot | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          Dot
+        </h1>
+<div class="m-graph m-warning">
+<svg style="width: 3.875rem; height: 11.875rem;" viewBox="0.00 0.00 62.00 190.31">
+<g transform="scale(1 1) rotate(0) translate(4 186.309)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-163.924" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-160.124">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-91.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-87.3543">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&gt;b</title>
+<path d="M27,-145.43C27,-137.748 27,-128.539 27,-119.955"/>
+<polygon points="30.5001,-119.938 27,-109.938 23.5001,-119.938 30.5001,-119.938"/>
+</g>
+<g class="m-node m-flat">
+<title>c</title>
+<ellipse cx="27" cy="-18.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">c</text>
+</g>
+<g class="m-edge">
+<title>b&#45;&gt;c</title>
+<path d="M27,-72.6607C27,-64.9784 27,-55.7693 27,-47.185"/>
+<polygon points="30.5001,-47.1687 27,-37.1687 23.5001,-47.1687 30.5001,-47.1687"/>
+</g>
+</g>
+</svg>
+</div>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/page_plugins/dot.html b/documentation/test_python/page_plugins/dot.html
new file mode 100644 (file)
index 0000000..6f6a00b
--- /dev/null
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Dot | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          Dot
+        </h1>
+<div class="m-graph m-warning">
+<svg style="width: 3.875rem; height: 11.875rem;" viewBox="0.00 0.00 62.00 190.31">
+<g transform="scale(1 1) rotate(0) translate(4 186.3087)">
+<g class="m-node m-flat">
+<title>a</title>
+<ellipse cx="27" cy="-163.9239" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-160.1239">a</text>
+</g>
+<g class="m-node m-flat">
+<title>b</title>
+<ellipse cx="27" cy="-91.1543" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-87.3543">b</text>
+</g>
+<g class="m-edge">
+<title>a&#45;&gt;b</title>
+<path d="M27,-145.1839C27,-137.3999 27,-128.1942 27,-119.6079"/>
+<polygon points="30.5001,-119.5788 27,-109.5789 23.5001,-119.5789 30.5001,-119.5788"/>
+</g>
+<g class="m-node m-flat">
+<title>c</title>
+<ellipse cx="27" cy="-18.3848" rx="27" ry="18.2703"/>
+<text text-anchor="middle" x="27" y="-14.5848">c</text>
+</g>
+<g class="m-edge">
+<title>b&#45;&gt;c</title>
+<path d="M27,-72.4144C27,-64.6303 27,-55.4246 27,-46.8383"/>
+<polygon points="30.5001,-46.8093 27,-36.8093 23.5001,-46.8094 30.5001,-46.8093"/>
+</g>
+</g>
+</svg>
+</div>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/page_plugins/dot.rst b/documentation/test_python/page_plugins/dot.rst
new file mode 100644 (file)
index 0000000..d9e8da7
--- /dev/null
@@ -0,0 +1,7 @@
+Dot
+###
+
+.. digraph::
+    :class: m-warning
+
+    a -> b -> c
diff --git a/documentation/test_python/page_plugins/index.html b/documentation/test_python/page_plugins/index.html
new file mode 100644 (file)
index 0000000..7fd76ab
--- /dev/null
@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>This shows some plugins | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          This shows some plugins
+        </h1>
+<p>This project is <a href="https://github.com/mosra/m.css">on GitHub</a> and</p>
+<table class="m-table">
+<thead>
+<tr><th>The</th>
+<th>tables</th>
+</tr>
+</thead>
+<tbody>
+<tr><td>render</td>
+<td>nicer now.</td>
+</tr>
+</tbody>
+</table>
+<aside class="m-note m-success">
+Yup!</aside>
+<p>See, <a href="https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawElements.xhtml">glDrawElements()</a> is the grandpa of <a href="https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/vkCmdDraw.html">vkCmdDraw()</a>. But
+<a class="m-flat" href="javascript:alert(&quot;boo!&quot;)">not everything is as it “seems” to be</a>
+— however the typography makes that bearable. Python bindings for
+<a href="https://doc.magnum.graphics/corrade/namespaceCorrade_1_1Containers.html">Corrade::Containers</a> and Magnum are nice too:</p>
+<pre class="m-code"><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">magnum</span> <span class="kn">import</span> <span class="o">*</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">a</span> <span class="o">=</span> <span class="n">Vector3</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">2.0</span><span class="p">,</span> <span class="mf">3.0</span><span class="p">)</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">a</span><span class="o">.</span><span class="n">dot</span><span class="p">()</span>
+<span class="go">14.0</span></pre>
+<p class="m-transition">~~~ Custom plugins! ~~~</p>
+<p>And now something totally different:</p>
+<style>
+div.m-plot svg { font-family: DejaVu Sans; }
+</style><div class="m-plot">
+<svg viewBox="0 0 576 113.76">
+ <defs>
+  <style type="text/css">
+*{stroke-linecap:butt;stroke-linejoin:round;}
+  </style>
+ </defs>
+ <g id="figure_1">
+  <g id="axes_1">
+   <g id="patch_1">
+    <path d="M 59.365156 69.588125 L 560.62 69.588125 L 560.62 27.757969 L 59.365156 27.757969 z" class="m-background"/>
+   </g>
+   <g id="plot1-value0"><title>15.0 meters, i guess?</title>
+    <path clip-path="url(#p2abd2612b1)" d="M 59.365156 29.659339 L 298.057939 29.659339 L 298.057939 46.560413 L 59.365156 46.560413 z" class="m-bar m-success"/>
+   </g>
+   <g id="plot1-value1"><title>30.0 meters, i guess?</title>
+    <path clip-path="url(#p2abd2612b1)" d="M 59.365156 50.785681 L 536.750722 50.785681 L 536.750722 67.686754 L 59.365156 67.686754 z" class="m-bar m-success"/>
+   </g>
+   <g id="matplotlib.axis_1">
+    <g id="xtick_1">
+     <g id="line2d_1">
+      <defs>
+       <path d="M 0 0 L 0 3.5" id="m15f0a304df" class="m-line"/>
+      </defs>
+      <g>
+       <use x="59.365156" xlink:href="#m15f0a304df" y="69.588125"/>
+      </g>
+     </g>
+     <g id="text_1">
+      <text class="m-label" style="text-anchor:middle;" transform="rotate(-0, 59.365156, 84.946406)" x="59.365156" y="84.946406">0</text>
+     </g>
+    </g>
+    <g id="xtick_2">
+     <g id="line2d_2">
+      <g>
+       <use x="138.929417" xlink:href="#m15f0a304df" y="69.588125"/>
+      </g>
+     </g>
+     <g id="text_2">
+      <text class="m-label" style="text-anchor:middle;" transform="rotate(-0, 138.929417, 84.946406)" x="138.929417" y="84.946406">5</text>
+     </g>
+    </g>
+    <g id="xtick_3">
+     <g id="line2d_3">
+      <g>
+       <use x="218.493678" xlink:href="#m15f0a304df" y="69.588125"/>
+      </g>
+     </g>
+     <g id="text_3">
+      <text class="m-label" style="text-anchor:middle;" transform="rotate(-0, 218.493678, 84.946406)" x="218.493678" y="84.946406">10</text>
+     </g>
+    </g>
+    <g id="xtick_4">
+     <g id="line2d_4">
+      <g>
+       <use x="298.057939" xlink:href="#m15f0a304df" y="69.588125"/>
+      </g>
+     </g>
+     <g id="text_4">
+      <text class="m-label" style="text-anchor:middle;" transform="rotate(-0, 298.057939, 84.946406)" x="298.057939" y="84.946406">15</text>
+     </g>
+    </g>
+    <g id="xtick_5">
+     <g id="line2d_5">
+      <g>
+       <use x="377.6222" xlink:href="#m15f0a304df" y="69.588125"/>
+      </g>
+     </g>
+     <g id="text_5">
+      <text class="m-label" style="text-anchor:middle;" transform="rotate(-0, 377.6222, 84.946406)" x="377.6222" y="84.946406">20</text>
+     </g>
+    </g>
+    <g id="xtick_6">
+     <g id="line2d_6">
+      <g>
+       <use x="457.186461" xlink:href="#m15f0a304df" y="69.588125"/>
+      </g>
+     </g>
+     <g id="text_6">
+      <text class="m-label" style="text-anchor:middle;" transform="rotate(-0, 457.186461, 84.946406)" x="457.186461" y="84.946406">25</text>
+     </g>
+    </g>
+    <g id="xtick_7">
+     <g id="line2d_7">
+      <g>
+       <use x="536.750722" xlink:href="#m15f0a304df" y="69.588125"/>
+      </g>
+     </g>
+     <g id="text_7">
+      <text class="m-label" style="text-anchor:middle;" transform="rotate(-0, 536.750722, 84.946406)" x="536.750722" y="84.946406">30</text>
+     </g>
+    </g>
+    <g id="text_8">
+     <text class="m-label" style="text-anchor:middle;" transform="rotate(-0, 309.992578, 99.592344)" x="309.992578" y="99.592344">meters, i guess?</text>
+    </g>
+   </g>
+   <g id="matplotlib.axis_2">
+    <g id="ytick_1">
+     <g id="line2d_8">
+      <defs>
+       <path d="M 0 0 L -3.5 0" id="mba4ce04b6c" class="m-line"/>
+      </defs>
+      <g>
+       <use x="59.365156" xlink:href="#mba4ce04b6c" y="38.109876"/>
+      </g>
+     </g>
+     <g id="text_9">
+      <text class="m-label" style="text-anchor:end;" transform="rotate(-0, 52.365156, 42.289017)" x="52.365156" y="42.289017">First</text>
+     </g>
+    </g>
+    <g id="ytick_2">
+     <g id="line2d_9">
+      <g>
+       <use x="59.365156" xlink:href="#mba4ce04b6c" y="59.236218"/>
+      </g>
+     </g>
+     <g id="text_10">
+      <text class="m-label" style="text-anchor:end;" transform="rotate(-0, 52.365156, 63.415358)" x="52.365156" y="63.415358">Second</text>
+     </g>
+    </g>
+   </g>
+   <g id="text_11">
+    <text class="m-title" style="text-anchor:middle;" transform="rotate(-0, 309.992578, 21.757969)" x="309.992578" y="21.757969">A plot with a single color</text>
+   </g>
+  </g>
+ </g>
+ <defs>
+  <clipPath id="p2abd2612b1">
+   <rect height="41.830156" width="501.254844" x="59.365156" y="27.757969"/>
+  </clipPath>
+ </defs>
+</svg>
+</div>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
diff --git a/documentation/test_python/page_plugins/index.rst b/documentation/test_python/page_plugins/index.rst
new file mode 100644 (file)
index 0000000..52b3eb3
--- /dev/null
@@ -0,0 +1,50 @@
+This shows some plugins
+#######################
+
+This project is :gh:`on GitHub <mosra/m.css>` and
+
+.. class:: m-table
+
+====== ======
+The    tables
+====== ======
+render nicer now.
+====== ======
+
+.. note-success::
+
+    Yup!
+
+.. role:: link-flat(link)
+    :class: m-flat
+
+See, :glfn:`DrawElements` is the grandpa of :vkfn:`CmdDraw`. But
+:link-flat:`not everything is as it "seems" to be <javascript:alert("boo!")>`
+--- however the typography makes that bearable. Python bindings for
+:dox:`Corrade::Containers` and Magnum are nice too:
+
+.. code:: pycon
+
+    >>> from magnum import *
+    >>> a = Vector3(1.0, 2.0, 3.0)
+    >>> a.dot()
+    14.0
+
+.. fancy-line:: Custom plugins!
+
+And now something totally different:
+
+.. raw:: html
+
+    <style>
+    div.m-plot svg { font-family: DejaVu Sans; }
+    </style>
+
+.. plot:: A plot with a single color
+    :type: barh
+    :labels:
+        First
+        Second
+    :units: meters, i guess?
+    :values: 15 30
+    :colors: success
diff --git a/documentation/test_python/page_plugins/plugins/fancyline.py b/documentation/test_python/page_plugins/plugins/fancyline.py
new file mode 100644 (file)
index 0000000..82ea559
--- /dev/null
@@ -0,0 +1,43 @@
+#
+#   This file is part of m.css.
+#
+#   Copyright © 2017, 2018, 2019 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.
+#
+
+from docutils.parsers import rst
+from docutils.parsers.rst import directives
+from docutils.parsers.rst.roles import set_classes
+from docutils import nodes
+
+class FancyLine(rst.Directive):
+    final_argument_whitespace = True
+    has_content = False
+    required_arguments = 1
+
+    def run(self):
+        text = '~~~ {} ~~~'.format(self.arguments[0])
+        title_nodes, _ = self.state.inline_text(text, self.lineno)
+        node = nodes.paragraph('', '', *title_nodes)
+        node['classes'] += ['m-transition']
+        return [node]
+
+def register_mcss(**kwargs):
+    rst.directives.register_directive('fancy-line', FancyLine)
index 6298025a30ce05f7ce0a0b197ac03536579049b5..60fc7ed1063c139554f3c40e022d5b55db896714 100644 (file)
 #   DEALINGS IN THE SOFTWARE.
 #
 
+import os
+import re
+import subprocess
+
+from distutils.version import LooseVersion
+
 from . import BaseTestCase
 
+def dot_version():
+    return re.match(".*version (?P<version>\d+\.\d+\.\d+).*", subprocess.check_output(['dot', '-V'], stderr=subprocess.STDOUT).decode('utf-8').strip()).group('version')
+
 class Page(BaseTestCase):
     def __init__(self, *args, **kwargs):
         super().__init__(__file__, '', *args, **kwargs)
@@ -47,3 +56,36 @@ class PageInputSubdir(BaseTestCase):
         })
         # The same output as Page, just the file is taken from elsewhere
         self.assertEqual(*self.actual_expected_contents('index.html', '../page/index.html'))
+
+class Plugins(BaseTestCase):
+    def __init__(self, *args, **kwargs):
+        super().__init__(__file__, 'plugins', *args, **kwargs)
+
+    def test(self):
+        self.run_python({
+            # Test all of them to check the registration works well
+            'PLUGINS': [
+                'm.abbr',
+                'm.code',
+                'm.components',
+                'm.dot',
+                'm.dox',
+                'm.gh',
+                'm.gl',
+                'm.link',
+                'm.plots',
+                'm.vk',
+                'fancyline'
+            ],
+            'PLUGIN_PATHS': ['plugins'],
+            'INPUT_PAGES': ['index.rst', 'dot.rst'],
+            'M_HTMLSANITY_SMART_QUOTES': True,
+            'M_DOT_FONT': 'DejaVu Sans',
+            'M_PLOTS_FONT': 'DejaVu Sans',
+            'M_DOX_TAGFILES': [
+                (os.path.join(self.path, '../../../doc/documentation/corrade.tag'), 'https://doc.magnum.graphics/corrade/')
+            ]
+        })
+        self.assertEqual(*self.actual_expected_contents('index.html'))
+        # The output is different for older Graphviz
+        self.assertEqual(*self.actual_expected_contents('dot.html', 'dot.html' if LooseVersion(dot_version()) >= LooseVersion("2.40.1") else 'dot-238.html'))
index 6faeecf6dbe96f8d4220767ae7e87423f5c07275..80c4197d3586406ee8bcb99d8a707fb36303c258 100644 (file)
@@ -45,5 +45,7 @@ def abbr(name, rawtext, text, lineno, inliner, options={}, content=[]):
         return [nodes.abbreviation(title, title, **options)], []
     return [nodes.abbreviation(abbr, abbr, title=title, **options)], []
 
-def register():
+def register_mcss(**kwargs):
     rst.roles.register_local_role('abbr', abbr)
+
+register = register_mcss # for Pelican
index 97d16dc096862f20d5de66f26c330ed11f8d960d..084967302d4ceeca817654776bcba11c203d605a 100644 (file)
@@ -66,8 +66,10 @@ class AliasGenerator:
                 with open(alias_file, 'w') as f:
                     f.write("""<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0;url={}" /></head></html>\n""".format(alias_target))
 
-def get_generators(generators): return AliasGenerator
+def register_mcss(**kwargs):
+    assert not "This plugin is Pelican-only" # pragma: no cover
 
-def register():
-    # TODO: why `lambda generators: AliasGenerator` doesn't work?
-    signals.get_generators.connect(get_generators)
+def _pelican_get_generators(generators): return AliasGenerator
+
+def register(): # for Pelican
+    signals.get_generators.connect(_pelican_get_generators)
index 4bb427400d2888d8b5a5c80105457e2443862bdd..54ba1aa2366469431bee187f49b66f2364d378fd 100644 (file)
@@ -211,7 +211,9 @@ def code(role, rawtext, text, lineno, inliner, options={}, content=[]):
 code.options = {'class': directives.class_option,
                 'language': directives.unchanged}
 
-def register():
+def register_mcss(**kwargs):
     rst.directives.register_directive('code', Code)
     rst.directives.register_directive('include', Include)
     rst.roles.register_canonical_role('code', code)
+
+register = register_mcss # for Pelican
index 694c32a339103542e30cbc0cfc9167703571626f..c20cd4d5da3bbe8828f41e06b39121989d68c009 100644 (file)
@@ -366,7 +366,7 @@ def label_flat_info(name, rawtext, text, lineno, inliner, options={}, content=[]
 def label_flat_dim(name, rawtext, text, lineno, inliner, options={}, content=[]):
     return label(['m-flat', 'm-dim'], name, rawtext, text, lineno, inliner, options, content)
 
-def register():
+def register_mcss(**kwargs):
     rst.directives.register_directive('transition', Transition)
 
     rst.directives.register_directive('note-default', DefaultNote)
@@ -424,3 +424,5 @@ def register():
     rst.roles.register_canonical_role('label-flat-danger', label_flat_danger)
     rst.roles.register_canonical_role('label-flat-info', label_flat_info)
     rst.roles.register_canonical_role('label-flat-dim', label_flat_dim)
+
+register = register_mcss # for Pelican
index 0efd1324a1c9c31c55797aee3a65e1637422876e..851e5eca395ed2eb7f07aa2defdf22bb649d15c9 100644 (file)
@@ -102,14 +102,17 @@ class StrictGraph(Dot):
             self.arguments[0] if self.arguments else '',
             '\n'.join(self.content)))
 
-def configure(pelicanobj):
+def register_mcss(mcss_settings, **kwargs):
     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)
+        mcss_settings.get('M_DOT_FONT', 'Source Sans Pro'),
+        mcss_settings.get('M_DOT_FONT_SIZE', 16.0))
     rst.directives.register_directive('digraph', Digraph)
     rst.directives.register_directive('strict-digraph', StrictDigraph)
     rst.directives.register_directive('graph', Graph)
     rst.directives.register_directive('strict-graph', StrictGraph)
+
+def _pelican_configure(pelicanobj):
+    register_mcss(mcss_settings=pelicanobj.settings)
+
+def register(): # for Pelican
+    pelican.signals.initialized.connect(_pelican_configure)
index 28dbf8669ca1ddaf203a2d0a6930516e6d80000e..2ce5ec71ce8dbc0ad8a189120df3ef60eaf19b17 100644 (file)
@@ -50,10 +50,10 @@ def parse_link(text):
 
     return title, link, hash
 
-def init(pelicanobj):
-    global symbol_mapping, symbol_prefixes, tagfile_basenames
+def init(tagfiles, input):
+    rst.roles.register_local_role('dox', dox)
 
-    tagfiles = pelicanobj.settings.get('M_DOX_TAGFILES', [])
+    global symbol_mapping, symbol_prefixes, tagfile_basenames
 
     # Pre-round to populate subclasses. Clear everything in case we init'd
     # before already.
@@ -69,7 +69,7 @@ def init(pelicanobj):
         tagfile_basenames += [(os.path.splitext(os.path.basename(tagfile))[0], path, css_classes)]
         symbol_prefixes += prefixes
 
-        tree = ET.parse(tagfile)
+        tree = ET.parse(os.path.join(input, tagfile))
         root = tree.getroot()
         for child in root:
             if child.tag == 'compound' and 'kind' in child.attrib:
@@ -170,7 +170,19 @@ def dox(name, rawtext, text, lineno, inliner: Inliner, options={}, content=[]):
         node = nodes.literal(rawtext, target, **_options)
     return [node], []
 
-def register():
-    signals.initialized.connect(init)
+def register_mcss(mcss_settings, **kwargs):
+    init(input=mcss_settings['INPUT'],
+         tagfiles=mcss_settings.get('M_DOX_TAGFILES', []))
 
-    rst.roles.register_local_role('dox', dox)
+def _pelican_configure(pelicanobj):
+    settings = {
+        # For backwards compatibility, the input directory is pelican's CWD
+        'INPUT': os.getcwd(),
+    }
+    for key in ['M_DOX_TAGFILES']:
+        if key in pelicanobj.settings: settings[key] = pelicanobj.settings[key]
+
+    register_mcss(mcss_settings=settings)
+
+def register(): # for Pelican
+    signals.initialized.connect(_pelican_configure)
index c23d17df34d41e9598901aa5eb1cc5ad59f9e044..2004b80701d66385dec808033613b2215f7d5370 100644 (file)
@@ -31,13 +31,9 @@ from pelican import signals
 
 settings = {}
 
-def init(pelicanobj):
-    settings['path'] = pelicanobj.settings.get('PATH', 'content')
-
 def filesize(name, rawtext, text, lineno, inliner, options={}, content=[]):
     # Support both {filename} (3.7.1) and {static} (3.8) placeholders
-    file = os.path.join(os.getcwd(), settings['path'])
-    size = os.path.getsize(text.format(filename=file, static=file))
+    size = os.path.getsize(text.format(filename=settings['INPUT'], static=settings['INPUT']))
 
     for unit in ['','k','M','G','T']:
         if abs(size) < 1024.0:
@@ -51,8 +47,7 @@ def filesize(name, rawtext, text, lineno, inliner, options={}, content=[]):
 
 def filesize_gz(name, rawtext, text, lineno, inliner, options={}, content=[]):
     # Support both {filename} (3.7.1) and {static} (3.8) placeholders
-    file = os.path.join(os.getcwd(), settings['path'])
-    with open(text.format(filename=file, static=file), mode='rb') as f:
+    with open(text.format(filename=settings['INPUT'], static=settings['INPUT']), mode='rb') as f:
         size = len(gzip.compress(f.read()))
 
     for unit in ['','k','M','G','T']:
@@ -65,8 +60,17 @@ def filesize_gz(name, rawtext, text, lineno, inliner, options={}, content=[]):
     set_classes(options)
     return [nodes.inline(size_string, size_string, **options)], []
 
-def register():
-    signals.initialized.connect(init)
-
+def register_mcss(mcss_settings, **kwargs):
+    global settings
+    settings['INPUT'] = mcss_settings['INPUT']
     rst.roles.register_local_role('filesize', filesize)
     rst.roles.register_local_role('filesize-gz', filesize_gz)
+
+def _pelican_configure(pelicanobj):
+    settings = {
+        'INPUT': os.path.join(os.getcwd(), pelicanobj.settings['PATH'])
+    }
+    register_mcss(mcss_settings=settings)
+
+def register(): # for Pelican
+    signals.initialized.connect(_pelican_configure)
index 9ef7249c83366a7937de44f3e6926d3ef8b9f3d2..145c2e3df02539d32298468877d76b0771f5c7e6 100644 (file)
@@ -77,5 +77,7 @@ def gh(name, rawtext, text, lineno, inliner, options={}, content=[]):
     node = nodes.reference(rawtext, title, refuri=url, **options)
     return [node], []
 
-def register():
+def register_mcss(**kwargs):
     rst.roles.register_local_role('gh', gh)
+
+register = register_mcss # for Pelican
index 31b155cc1d50f3bca076efd95889da50345510e4..a0ceb35d7dee633421ebf270c06eeabeaa0f4244 100644 (file)
@@ -71,8 +71,10 @@ def glfnext(name, rawtext, text, lineno, inliner, options={}, content=[]):
     node = nodes.reference(rawtext, "gl" + title + prefix + "()", refuri=url, **options)
     return [node], []
 
-def register():
+def register_mcss(**kwargs):
     rst.roles.register_local_role('glext', glext)
     rst.roles.register_local_role('webglext', webglext)
     rst.roles.register_local_role('glfn', glfn)
     rst.roles.register_local_role('glfnext', glfnext)
+
+register = register_mcss # for Pelican
index 062606af5adff9182df3e8b8d0e707ade3d611b1..eb768a33b567cb49b53ba51cef7fde50d8194e23 100644 (file)
@@ -22,6 +22,7 @@
 #   DEALINGS IN THE SOFTWARE.
 #
 
+import copy
 import os
 from docutils.parsers import rst
 from docutils.parsers.rst import Directive
@@ -40,7 +41,12 @@ try:
 except ImportError:
     PIL = None
 
-settings = {}
+default_settings = {
+    'INPUT': None,
+    'M_IMAGES_REQUIRE_ALT_TEXT': False
+}
+
+settings = None
 
 class Image(Directive):
     """Image directive
@@ -96,14 +102,15 @@ class Image(Directive):
         width = None
         height = None
         # If scaling requested, open the files and calculate the scaled size
-        # Support both {filename} (3.7.1) and {static} (3.8) placeholders. In
-        # all cases use only width and not both so the max-width can correctly
+        # Support both {filename} (3.7.1) and {static} (3.8) placeholders,
+        # also prepend the absolute path in case we're not Pelican. In all
+        # cases use only width and not both so the max-width can correctly
         # scale the image down on smaller screen sizes.
         # TODO: implement ratio-preserving scaling to avoid jumps on load using
         # the margin-bottom hack
         if 'scale' in self.options:
-            file = os.path.join(os.getcwd(), settings['PATH'])
-            absuri = reference.format(filename=file, static=file)
+            file = os.path.join(os.getcwd(), settings['INPUT'])
+            absuri = os.path.join(file, reference.format(filename=file, static=file))
             im = PIL.Image.open(absuri)
             width = "{}px".format(int(im.width*self.options['scale']/100.0))
         elif 'width' in self.options:
@@ -209,9 +216,10 @@ class ImageGrid(rst.Directive):
             uri, _, caption = uri_caption.partition(' ')
 
             # Open the files and calculate the overall width
-            # Support both {filename} (3.7.1) and {static} (3.8) placeholders
-            file = os.path.join(os.getcwd(), settings['PATH'])
-            absuri = uri.format(filename=file, static=file)
+            # Support both {filename} (3.7.1) and {static} (3.8) placeholders,
+            # also prepend the absolute path in case we're not Pelican
+            file = os.path.join(os.getcwd(), settings['INPUT'])
+            absuri = os.path.join(file, uri.format(filename=file, static=file))
             im = PIL.Image.open(absuri)
 
             # If no caption provided, get EXIF info, if it's there
@@ -271,13 +279,24 @@ class ImageGrid(rst.Directive):
 
         return [grid_node]
 
-def configure(pelicanobj):
-    settings['PATH'] = pelicanobj.settings.get('PATH', 'content')
-    settings['M_IMAGES_REQUIRE_ALT_TEXT'] = pelicanobj.settings.get('M_IMAGES_REQUIRE_ALT_TEXT', False)
-
-def register():
-    signals.initialized.connect(configure)
+def register_mcss(mcss_settings, **kwargs):
+    global default_settings, settings
+    settings = copy.deepcopy(default_settings)
+    for key in settings.keys():
+        if key in mcss_settings: settings[key] = mcss_settings[key]
 
     rst.directives.register_directive('image', Image)
     rst.directives.register_directive('figure', Figure)
     rst.directives.register_directive('image-grid', ImageGrid)
+
+def _pelican_configure(pelicanobj):
+    settings = {
+        'INPUT': pelicanobj.settings['PATH'],
+    }
+    for key in 'M_IMAGES_REQUIRE_ALT_TEXT':
+        if key in pelicanobj.settings: settings[key] = pelicanobj.settings[key]
+
+    register_mcss(mcss_settings=settings)
+
+def register(): # for Pelican
+    signals.initialized.connect(_pelican_configure)
index e710c0b62aeee815dc821bcafce12a91cfa6c9e2..0569a08522d5eb5cf35ea6345c6a2bcc042abb71 100644 (file)
@@ -46,5 +46,7 @@ def link(name, rawtext, text, lineno, inliner, options={}, content=[]):
     node = nodes.reference(rawtext, title, refuri=url, **options)
     return [node], []
 
-def register():
+def register_mcss(**kwargs):
     rst.roles.register_local_role('link', link)
+
+register = register_mcss # for Pelican
index b83a8fb17b9d11dd3dac7ef230571010be9cd229..151116d6ed1976e6ac88d4bbabf979a557d1788d 100644 (file)
@@ -22,6 +22,7 @@
 #   DEALINGS IN THE SOFTWARE.
 #
 
+import copy
 import html
 import os
 import re
@@ -36,7 +37,13 @@ import pelican.signals
 import latex2svg
 import latex2svgextra
 
-render_as_code = False
+default_settings = {
+    'INPUT': '',
+    'M_MATH_RENDER_AS_CODE': False,
+    'M_MATH_CACHE_FILE': 'm.math.cache'
+}
+
+settings = None
 
 def _is_math_figure(parent):
     # The parent has to be a figure, marked as m-figure
@@ -61,7 +68,7 @@ class Math(rst.Directive):
         parent = self.state.parent
 
         # Fallback rendering as code requested
-        if render_as_code:
+        if settings['M_MATH_RENDER_AS_CODE']:
             # If this is a math figure, replace the figure CSS class to have a
             # matching border
             if _is_math_figure(parent):
@@ -102,7 +109,7 @@ def math(role, rawtext, text, lineno, inliner, options={}, content=[]):
     text = rawtext.split('`')[1]
 
     # Fallback rendering as code requested
-    if render_as_code:
+    if settings['M_MATH_RENDER_AS_CODE']:
         set_classes(options)
         classes = []
         if 'classes' in options:
@@ -127,22 +134,31 @@ def math(role, rawtext, text, lineno, inliner, options={}, content=[]):
     node = nodes.raw(rawtext, latex2svgextra.patch(text, svg, depth, attribs), format='html', **options)
     return [node], []
 
-def configure_pelican(pelicanobj):
-    global render_as_code
-    render_as_code = pelicanobj.settings.get('M_MATH_RENDER_AS_CODE', False)
-    cache_file = pelicanobj.settings.get('M_MATH_CACHE_FILE', 'm.math.cache')
-    if cache_file and os.path.exists(cache_file):
-        latex2svgextra.unpickle_cache(cache_file)
-    else:
-        latex2svgextra.unpickle_cache(None)
-
 def save_cache(pelicanobj):
-    cache_file = pelicanobj.settings.get('M_MATH_CACHE_FILE', 'm.math.cache')
-    if cache_file: latex2svgextra.pickle_cache(cache_file)
+    if settings['M_MATH_CACHE_FILE']:
+        latex2svgextra.pickle_cache(settings['M_MATH_CACHE_FILE'])
+
+def register_mcss(mcss_settings, **kwargs):
+    global default_settings, settings
+    settings = copy.deepcopy(default_settings)
+    for key in settings.keys():
+        if key in mcss_settings: settings[key] = mcss_settings[key]
+
+    if settings['M_MATH_CACHE_FILE']:
+        settings['M_MATH_CACHE_FILE'] = os.path.join(settings['INPUT'], settings['M_MATH_CACHE_FILE'])
+
+        if os.path.exists(settings['M_MATH_CACHE_FILE']):
+            latex2svgextra.unpickle_cache(settings['M_MATH_CACHE_FILE'])
+        else:
+            latex2svgextra.unpickle_cache(None)
+
+    rst.directives.register_directive('math', Math)
+    rst.roles.register_canonical_role('math', math)
+
+def _configure_pelican(pelicanobj):
+    register_mcss(mcss_settings=pelicanobj.settings)
 
 def register():
-    pelican.signals.initialized.connect(configure_pelican)
+    pelican.signals.initialized.connect(_configure_pelican)
     pelican.signals.finalized.connect(save_cache)
     pelican.signals.content_object_init.connect(new_page)
-    rst.directives.register_directive('math', Math)
-    rst.roles.register_canonical_role('math', math)
index e170e595cf8020d2a649cc11817cd7593dfe560e..5fa4e1ef433ee73bf3d62f6f22bc382f78af0ea3 100644 (file)
@@ -77,5 +77,8 @@ def populate_metadata(article_generator):
         if hasattr(page, 'title'):
             article.category.badge_title = page.title
 
-def register():
+def register_mcss(**kwargs):
+    assert not "This plugin is Pelican-only" # pragma: no cover
+
+def register(): # for Pelican
     signals.article_generator_finalized.connect(populate_metadata)
index 06b38ba6b4046d2e7c428ae3bf77513612fd5724..5bf71c1357eebc874a83de7172104f7643e813be 100644 (file)
@@ -241,14 +241,18 @@ class Plot(rst.Directive):
 def new_page(content):
     mpl.rcParams['svg.hashsalt'] = 0
 
-def configure(pelicanobj):
-    font = pelicanobj.settings.get('M_PLOTS_FONT', 'Source Sans Pro')
+def register_mcss(mcss_settings, **kwargs):
+    font = mcss_settings.get('M_PLOTS_FONT', 'Source Sans Pro')
     for i in range(len(_class_mapping)):
         src, dst = _class_mapping[i]
         _class_mapping[i] = (src.format(font=font), dst)
     mpl.rcParams['font.family'] = font
 
-def register():
-    pelican.signals.initialized.connect(configure)
-    pelican.signals.content_object_init.connect(new_page)
     rst.directives.register_directive('plot', Plot)
+
+def _pelican_configure(pelicanobj):
+    register_mcss(mcss_settings=pelicanobj.settings)
+
+def register(): # for Pelican
+    pelican.signals.initialized.connect(_pelican_configure)
+    pelican.signals.content_object_init.connect(new_page)
index 00640dd7f4693621324f724e1ff4d94762c642c2..a5bc2c676704d1f44836e2d0470f10733b5b20d2 100644 (file)
@@ -62,7 +62,9 @@ def vktype(name, rawtext, text, lineno, inliner, options={}, content=[]):
     node = nodes.reference(rawtext, title, refuri=url, **options)
     return [node], []
 
-def register():
+def register_mcss(**kwargs):
     rst.roles.register_local_role('vkext', vkext)
     rst.roles.register_local_role('vkfn', vkfn)
     rst.roles.register_local_role('vktype', vktype)
+
+register = register_mcss # for Pelican