chiark / gitweb /
documentation/python: add support for pre-page and post-run hooks.
authorVladimír Vondruš <mosra@centrum.cz>
Sun, 12 May 2019 21:01:10 +0000 (23:01 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Tue, 21 May 2019 15:19:25 +0000 (17:19 +0200)
doc/documentation/python.rst
documentation/python.py
documentation/test_python/page_plugins/plugins/fancyline.py
documentation/test_python/test_page.py
plugins/m/math.py
plugins/m/plots.py

index 8c648ac0a551b27ff8925e1ba8365f2a27201351..bb9a588549ed0bdc894641c806fb2cf951d3f5f0 100644 (file)
@@ -618,6 +618,8 @@ Keyword argument            Content
 :py:`module_doc_contents`   Module documentation contents
 :py:`class_doc_contents`    Class documentation contents
 :py:`data_doc_contents`     Data documentation contents
+:py:`hooks_pre_page`        Hooks to call before each page gets rendered
+:py:`hooks_post_run`        Hooks to call at the very end of the script run
 =========================== ===================================================
 
 The :py:`module_doc_contents`, :py:`class_doc_contents` and
@@ -635,10 +637,18 @@ source shown in the `External documentation content`_ section below.
         'details': "This class is *pretty*."
     }
 
+The :py:`hooks_pre_page` and :py:`hooks_post_run` variables are lists of
+parameter-less functions. Plugins that need to do something before each page
+of output gets rendered (for example, resetting an some internal counter for
+page-wide unique element IDs) or after the whole run is done (for example to
+serialize cached internal state) are supposed to add functions to the list.
+
 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.
+examples. The below example shows registration of a hypothetic HTML validator
+plugin --- it saves the output path from settings and registers a post-run hook
+that validates everything in given output directory.
 
 .. code:: py
 
@@ -646,9 +656,13 @@ examples.
 
     …
 
-    def register_mcss(mcss_settings, **kwargs):
+    def _validate_output():
+        validate_all_html_files(output_dir)
+
+    def register_mcss(mcss_settings, hooks_post_run, **kwargs):
         global output_dir
         output_dir = mcss_settings['OUTPUT']
+        hooks_post_run += [_validate_output]
 
 `External documentation content`_
 =================================
index 577d39de03aaae8cd0439fe4a305f3840c87cdcf..9d49ffbfc242ac12277b15bf40a5792b85b3404c 100755 (executable)
@@ -125,6 +125,9 @@ class State:
         self.data_docs: Dict[str, Dict[str, str]] = {}
         self.external_data: Set[str] = set()
 
+        self.hooks_pre_page: List = []
+        self.hooks_post_run: List = []
+
 def is_internal_function_name(name: str) -> bool:
     """If the function name is internal.
 
@@ -614,6 +617,9 @@ def extract_data_doc(state: State, parent, path: List[str], data):
 def render_module(state: State, path, module, env):
     logging.debug("generating %s.html", '.'.join(path))
 
+    # Call all registered page begin hooks
+    for hook in state.hooks_pre_page: hook()
+
     url_base = ''
     breadcrumb = []
     for i in path:
@@ -829,6 +835,9 @@ _filtered_builtin_properties = set([
 def render_class(state: State, path, class_, env):
     logging.debug("generating %s.html", '.'.join(path))
 
+    # Call all registered page begin hooks
+    for hook in state.hooks_pre_page: hook()
+
     url_base = ''
     breadcrumb = []
     for i in path:
@@ -977,6 +986,9 @@ def render_inline_rst(state: State, source):
 def render_doc(state: State, filename):
     logging.debug("parsing docs from %s", filename)
 
+    # Page begin hooks are called before this in run(), once for all docs since
+    # these functions are not generating any pages
+
     # Render the file. The directives should take care of everything, so just
     # discard the output afterwards.
     with open(filename, 'r') as f: publish_rst(state, f.read())
@@ -984,6 +996,9 @@ def render_doc(state: State, filename):
 def render_page(state: State, path, filename, env):
     logging.debug("generating %s.html", '.'.join(path))
 
+    # Call all registered page begin hooks
+    for hook in state.hooks_pre_page: hook()
+
     # Render the file
     with open(filename, 'r') as f: pub = publish_rst(state, f.read())
 
@@ -1076,7 +1091,12 @@ def run(basedir, config, templates):
             jinja_environment=env,
             module_doc_contents=state.module_docs,
             class_doc_contents=state.class_docs,
-            data_doc_contents=state.data_docs)
+            data_doc_contents=state.data_docs,
+            hooks_pre_page=state.hooks_pre_page,
+            hooks_post_run=state.hooks_post_run)
+
+    # Call all registered page begin hooks for the first time
+    for hook in state.hooks_pre_page: hook()
 
     # First process the doc input files so we have all data for rendering
     # module pages
@@ -1157,6 +1177,9 @@ def run(basedir, config, templates):
         logging.debug("copying %s to output", i)
         shutil.copy(i, os.path.join(config['OUTPUT'], os.path.basename(i)))
 
+    # Call all registered finalization hooks for the first time
+    for hook in state.hooks_post_run: hook()
+
 if __name__ == '__main__': # pragma: no cover
     parser = argparse.ArgumentParser()
     parser.add_argument('conf', help="configuration file")
index 82ea55907db8c015fe0e6969e3cada3a986f4c78..7970f902a1eb249f46b16620228f797498c288bd 100644 (file)
@@ -39,5 +39,19 @@ class FancyLine(rst.Directive):
         node['classes'] += ['m-transition']
         return [node]
 
-def register_mcss(**kwargs):
+pre_page_call_count = 0
+post_run_call_count = 0
+
+def _pre_page():
+    global pre_page_call_count
+    pre_page_call_count = pre_page_call_count + 1
+
+def _post_run():
+    global post_run_call_count
+    post_run_call_count = post_run_call_count + 1
+
+def register_mcss(hooks_pre_page, hooks_post_run, **kwargs):
+    hooks_pre_page += [_pre_page]
+    hooks_post_run += [_post_run]
+
     rst.directives.register_directive('fancy-line', FancyLine)
index 8d3ae7845d4485dcf8c6e67701eaa68f1000a3a3..6c6f3475520cb54cca4b2933f2334313bc6ad5af 100644 (file)
@@ -91,3 +91,7 @@ class Plugins(BaseTestCase):
         # 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'))
         self.assertTrue(os.path.exists(os.path.join(self.path, 'output/tiny.png')))
+
+        import fancyline
+        self.assertEqual(fancyline.pre_page_call_count, 3)
+        self.assertEqual(fancyline.post_run_call_count, 1)
index 151116d6ed1976e6ac88d4bbabf979a557d1788d..89209ec3772097a92fcb5ef46beb1c062fe7b6b0 100644 (file)
@@ -100,7 +100,7 @@ class Math(rst.Directive):
         container.append(node)
         return [container]
 
-def new_page(content):
+def new_page(*args):
     latex2svgextra.counter = 0
 
 def math(role, rawtext, text, lineno, inliner, options={}, content=[]):
@@ -134,11 +134,11 @@ 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 save_cache(pelicanobj):
+def save_cache(*args):
     if settings['M_MATH_CACHE_FILE']:
         latex2svgextra.pickle_cache(settings['M_MATH_CACHE_FILE'])
 
-def register_mcss(mcss_settings, **kwargs):
+def register_mcss(mcss_settings, hooks_pre_page, hooks_post_run, **kwargs):
     global default_settings, settings
     settings = copy.deepcopy(default_settings)
     for key in settings.keys():
@@ -152,11 +152,14 @@ def register_mcss(mcss_settings, **kwargs):
         else:
             latex2svgextra.unpickle_cache(None)
 
+    hooks_pre_page += [new_page]
+    hooks_post_run += [save_cache]
+
     rst.directives.register_directive('math', Math)
     rst.roles.register_canonical_role('math', math)
 
 def _configure_pelican(pelicanobj):
-    register_mcss(mcss_settings=pelicanobj.settings)
+    register_mcss(mcss_settings=pelicanobj.settings, hooks_pre_page=[], hooks_post_run=[])
 
 def register():
     pelican.signals.initialized.connect(_configure_pelican)
index 5bf71c1357eebc874a83de7172104f7643e813be..9c84fd71826977a7f8365f8c7315c446f45e68cb 100644 (file)
@@ -238,20 +238,22 @@ class Plot(rst.Directive):
         container.append(node)
         return [container]
 
-def new_page(content):
+def new_page(*args):
     mpl.rcParams['svg.hashsalt'] = 0
 
-def register_mcss(mcss_settings, **kwargs):
+def register_mcss(mcss_settings, hooks_pre_page, **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
 
+    hooks_pre_page += [new_page]
+
     rst.directives.register_directive('plot', Plot)
 
 def _pelican_configure(pelicanobj):
-    register_mcss(mcss_settings=pelicanobj.settings)
+    register_mcss(mcss_settings=pelicanobj.settings, hooks_pre_page=[])
 
 def register(): # for Pelican
     pelican.signals.initialized.connect(_pelican_configure)