From: Vladimír Vondruš Date: Sun, 12 May 2019 21:01:10 +0000 (+0200) Subject: documentation/python: add support for pre-page and post-run hooks. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~cjwatson/git?a=commitdiff_plain;h=6226e9f97cb275848ab39b1be0468c6a8883e4a9;p=blog.git documentation/python: add support for pre-page and post-run hooks. --- diff --git a/doc/documentation/python.rst b/doc/documentation/python.rst index 8c648ac0..bb9a5885 100644 --- a/doc/documentation/python.rst +++ b/doc/documentation/python.rst @@ -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`_ ================================= diff --git a/documentation/python.py b/documentation/python.py index 577d39de..9d49ffbf 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -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") diff --git a/documentation/test_python/page_plugins/plugins/fancyline.py b/documentation/test_python/page_plugins/plugins/fancyline.py index 82ea5590..7970f902 100644 --- a/documentation/test_python/page_plugins/plugins/fancyline.py +++ b/documentation/test_python/page_plugins/plugins/fancyline.py @@ -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) diff --git a/documentation/test_python/test_page.py b/documentation/test_python/test_page.py index 8d3ae784..6c6f3475 100644 --- a/documentation/test_python/test_page.py +++ b/documentation/test_python/test_page.py @@ -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) diff --git a/plugins/m/math.py b/plugins/m/math.py index 151116d6..89209ec3 100644 --- a/plugins/m/math.py +++ b/plugins/m/math.py @@ -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) diff --git a/plugins/m/plots.py b/plugins/m/plots.py index 5bf71c13..9c84fd71 100644 --- a/plugins/m/plots.py +++ b/plugins/m/plots.py @@ -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)