From 7da9f16733fa05658e5d9ae423dbb7f3d5e513ec Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 5 May 2019 16:05:10 +0200 Subject: [PATCH] documentation/python: support plugins. It looks like we're 90% there, hah. --- doc/documentation/python.rst | 53 +++++ doc/plugins.rst | 7 +- doc/plugins/components.rst | 10 + doc/plugins/htmlsanity.rst | 12 ++ doc/plugins/images.rst | 12 ++ doc/plugins/links.rst | 15 +- doc/plugins/math-and-code.rst | 14 ++ doc/plugins/plots-and-graphs.rst | 3 +- documentation/python.py | 15 +- .../test_python/page_plugins/dot-238.html | 61 ++++++ .../test_python/page_plugins/dot.html | 61 ++++++ .../test_python/page_plugins/dot.rst | 7 + .../test_python/page_plugins/index.html | 190 ++++++++++++++++++ .../test_python/page_plugins/index.rst | 50 +++++ .../page_plugins/plugins/fancyline.py | 43 ++++ documentation/test_python/test_page.py | 42 ++++ plugins/m/abbr.py | 4 +- plugins/m/alias.py | 10 +- plugins/m/code.py | 4 +- plugins/m/components.py | 4 +- plugins/m/dot.py | 15 +- plugins/m/dox.py | 26 ++- plugins/m/filesize.py | 24 ++- plugins/m/gh.py | 4 +- plugins/m/gl.py | 4 +- plugins/m/images.py | 47 +++-- plugins/m/link.py | 4 +- plugins/m/math.py | 50 +++-- plugins/m/metadata.py | 5 +- plugins/m/plots.py | 14 +- plugins/m/vk.py | 4 +- 31 files changed, 732 insertions(+), 82 deletions(-) create mode 100644 documentation/test_python/page_plugins/dot-238.html create mode 100644 documentation/test_python/page_plugins/dot.html create mode 100644 documentation/test_python/page_plugins/dot.rst create mode 100644 documentation/test_python/page_plugins/index.html create mode 100644 documentation/test_python/page_plugins/index.rst create mode 100644 documentation/test_python/page_plugins/plugins/fancyline.py diff --git a/doc/documentation/python.rst b/doc/documentation/python.rst index 00061f93..ffe9d39b 100644 --- a/doc/documentation/python.rst +++ b/doc/documentation/python.rst @@ -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 ` and exposed as HTML, otherwise as a plain text. +`Plugins`_ +========== + +The :abbr:`reST ` 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`_ ========================= diff --git a/doc/plugins.rst b/doc/plugins.rst index ddf585f2..b258d04d 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -61,9 +61,10 @@ below or :gh:`grab the whole Git repository `: :gh:`m.alias ` - :gh:`m.metadata ` -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 diff --git a/doc/plugins/components.rst b/doc/plugins/components.rst index c74150b8..f3ae7bca 100644 --- a/doc/plugins/components.rst +++ b/doc/plugins/components.rst @@ -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`_ ---------------- diff --git a/doc/plugins/htmlsanity.rst b/doc/plugins/htmlsanity.rst index a3367ea5..39291ea4 100644 --- a/doc/plugins/htmlsanity.rst +++ b/doc/plugins/htmlsanity.rst @@ -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`_ ---------------- diff --git a/doc/plugins/images.rst b/doc/plugins/images.rst index 3ced8805..c20a1782 100644 --- a/doc/plugins/images.rst +++ b/doc/plugins/images.rst @@ -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`_ ---------------- diff --git a/doc/plugins/links.rst b/doc/plugins/links.rst index 6e334b54..035e0de1 100644 --- a/doc/plugins/links.rst +++ b/doc/plugins/links.rst @@ -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 diff --git a/doc/plugins/math-and-code.rst b/doc/plugins/math-and-code.rst index 01497aac..e00dfadb 100644 --- a/doc/plugins/math-and-code.rst +++ b/doc/plugins/math-and-code.rst @@ -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 diff --git a/doc/plugins/plots-and-graphs.rst b/doc/plugins/plots-and-graphs.rst index 43f6ba23..de909042 100644 --- a/doc/plugins/plots-and-graphs.rst +++ b/doc/plugins/plots-and-graphs.rst @@ -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 diff --git a/documentation/python.py b/documentation/python.py index eb88ab24..15c3131a 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -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 index 00000000..6caced72 --- /dev/null +++ b/documentation/test_python/page_plugins/dot-238.html @@ -0,0 +1,61 @@ + + + + + Dot | My Python Project + + + + + +
+
+
+
+
+

+ Dot +

+
+ + + +a + +a + + +b + +b + + +a->b + + + + +c + +c + + +b->c + + + + + +
+
+
+
+
+ + diff --git a/documentation/test_python/page_plugins/dot.html b/documentation/test_python/page_plugins/dot.html new file mode 100644 index 00000000..6f6a00bb --- /dev/null +++ b/documentation/test_python/page_plugins/dot.html @@ -0,0 +1,61 @@ + + + + + Dot | My Python Project + + + + + +
+
+
+
+
+

+ Dot +

+
+ + + +a + +a + + +b + +b + + +a->b + + + + +c + +c + + +b->c + + + + + +
+
+
+
+
+ + diff --git a/documentation/test_python/page_plugins/dot.rst b/documentation/test_python/page_plugins/dot.rst new file mode 100644 index 00000000..d9e8da7b --- /dev/null +++ b/documentation/test_python/page_plugins/dot.rst @@ -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 index 00000000..7fd76ab0 --- /dev/null +++ b/documentation/test_python/page_plugins/index.html @@ -0,0 +1,190 @@ + + + + + This shows some plugins | My Python Project + + + + + +
+
+
+
+
+

+ This shows some plugins +

+

This project is on GitHub and

+ + + + + + + + + + + +
Thetables
rendernicer now.
+ +

See, glDrawElements() is the grandpa of vkCmdDraw(). But +not everything is as it “seems” to be +— however the typography makes that bearable. Python bindings for +Corrade::Containers and Magnum are nice too:

+
>>> from magnum import *
+>>> a = Vector3(1.0, 2.0, 3.0)
+>>> a.dot()
+14.0
+

~~~ Custom plugins! ~~~

+

And now something totally different:

+
+ + + + + + + + + + 15.0 meters, i guess? + + + 30.0 meters, i guess? + + + + + + + + + + + + + + 0 + + + + + + + + + + 5 + + + + + + + + + + 10 + + + + + + + + + + 15 + + + + + + + + + + 20 + + + + + + + + + + 25 + + + + + + + + + + 30 + + + + meters, i guess? + + + + + + + + + + + + + + First + + + + + + + + + + Second + + + + + A plot with a single color + + + + + + + + + +
+
+
+
+
+ + diff --git a/documentation/test_python/page_plugins/index.rst b/documentation/test_python/page_plugins/index.rst new file mode 100644 index 00000000..52b3eb33 --- /dev/null +++ b/documentation/test_python/page_plugins/index.rst @@ -0,0 +1,50 @@ +This shows some plugins +####################### + +This project is :gh:`on GitHub ` 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 ` +--- 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 + + + +.. 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 index 00000000..82ea5590 --- /dev/null +++ b/documentation/test_python/page_plugins/plugins/fancyline.py @@ -0,0 +1,43 @@ +# +# This file is part of m.css. +# +# Copyright © 2017, 2018, 2019 Vladimír Vondruš +# +# 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) diff --git a/documentation/test_python/test_page.py b/documentation/test_python/test_page.py index 6298025a..60fc7ed1 100644 --- a/documentation/test_python/test_page.py +++ b/documentation/test_python/test_page.py @@ -22,8 +22,17 @@ # 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\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')) diff --git a/plugins/m/abbr.py b/plugins/m/abbr.py index 6faeecf6..80c4197d 100644 --- a/plugins/m/abbr.py +++ b/plugins/m/abbr.py @@ -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 diff --git a/plugins/m/alias.py b/plugins/m/alias.py index 97d16dc0..08496730 100644 --- a/plugins/m/alias.py +++ b/plugins/m/alias.py @@ -66,8 +66,10 @@ class AliasGenerator: with open(alias_file, 'w') as f: f.write("""\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) diff --git a/plugins/m/code.py b/plugins/m/code.py index 4bb42740..54ba1aa2 100644 --- a/plugins/m/code.py +++ b/plugins/m/code.py @@ -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 diff --git a/plugins/m/components.py b/plugins/m/components.py index 694c32a3..c20cd4d5 100644 --- a/plugins/m/components.py +++ b/plugins/m/components.py @@ -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 diff --git a/plugins/m/dot.py b/plugins/m/dot.py index 0efd1324..851e5eca 100644 --- a/plugins/m/dot.py +++ b/plugins/m/dot.py @@ -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) diff --git a/plugins/m/dox.py b/plugins/m/dox.py index 28dbf866..2ce5ec71 100644 --- a/plugins/m/dox.py +++ b/plugins/m/dox.py @@ -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) diff --git a/plugins/m/filesize.py b/plugins/m/filesize.py index c23d17df..2004b807 100644 --- a/plugins/m/filesize.py +++ b/plugins/m/filesize.py @@ -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) diff --git a/plugins/m/gh.py b/plugins/m/gh.py index 9ef7249c..145c2e3d 100644 --- a/plugins/m/gh.py +++ b/plugins/m/gh.py @@ -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 diff --git a/plugins/m/gl.py b/plugins/m/gl.py index 31b155cc..a0ceb35d 100644 --- a/plugins/m/gl.py +++ b/plugins/m/gl.py @@ -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 diff --git a/plugins/m/images.py b/plugins/m/images.py index 062606af..eb768a33 100644 --- a/plugins/m/images.py +++ b/plugins/m/images.py @@ -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) diff --git a/plugins/m/link.py b/plugins/m/link.py index e710c0b6..0569a085 100644 --- a/plugins/m/link.py +++ b/plugins/m/link.py @@ -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 diff --git a/plugins/m/math.py b/plugins/m/math.py index b83a8fb1..151116d6 100644 --- a/plugins/m/math.py +++ b/plugins/m/math.py @@ -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) diff --git a/plugins/m/metadata.py b/plugins/m/metadata.py index e170e595..5fa4e1ef 100644 --- a/plugins/m/metadata.py +++ b/plugins/m/metadata.py @@ -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) diff --git a/plugins/m/plots.py b/plugins/m/plots.py index 06b38ba6..5bf71c13 100644 --- a/plugins/m/plots.py +++ b/plugins/m/plots.py @@ -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) diff --git a/plugins/m/vk.py b/plugins/m/vk.py index 00640dd7..a5bc2c67 100644 --- a/plugins/m/vk.py +++ b/plugins/m/vk.py @@ -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 -- 2.30.2