From 8314af05ae89103f67a28bac0ea3ee7337055eb0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 28 Aug 2019 22:31:29 +0200 Subject: [PATCH] m.sphinx: added a :p: role for parameter referencing. So tiny yet so useful. Doxygen, take a note. --- doc/plugins/sphinx.rst | 9 ++++++++- .../test_python/content/content.html | 2 +- documentation/test_python/content/docs.rst | 4 ++-- plugins/m/sphinx.py | 20 ++++++++++++++++--- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/doc/plugins/sphinx.rst b/doc/plugins/sphinx.rst index 5a81819e..fbade22f 100644 --- a/doc/plugins/sphinx.rst +++ b/doc/plugins/sphinx.rst @@ -313,10 +313,17 @@ function signature will cause a warning. Example: :param overwrite_existing: Overwrite existing value if already present in the container :return: The inserted tuple or the existing - key/value pair in case ``overwrite_existing`` is not set + key/value pair in case :p:`overwrite_existing` is not set The operation has a :math:`\mathcal{O}(\log{}n)` complexity. +.. block-success:: Referencing function parameters + + What's also shown in the above snippet is the :rst:`:p:` directive. It + looks the same as if you would write just :rst:```overwrite_existing```, + but in addition it checks the parameter name against current function signature, emitting a warning in case of a mismatch. This is useful to + ensure the documentation doesn't get out of sync with the actual signature. + For overloaded functions (such as those coming from pybind11), it's possible to specify the full signature to distinguish between particular overloads. Directives with the full signature have a priority, if no signature matches diff --git a/documentation/test_python/content/content.html b/documentation/test_python/content/content.html index 47506485..1d8573f8 100644 --- a/documentation/test_python/content/content.html +++ b/documentation/test_python/content/content.html @@ -213,7 +213,7 @@ or parsed in any way.

b - The second one + The second one is different from a c diff --git a/documentation/test_python/content/docs.rst b/documentation/test_python/content/docs.rst index 3c10487b..e9303f12 100644 --- a/documentation/test_python/content/docs.rst +++ b/documentation/test_python/content/docs.rst @@ -100,7 +100,7 @@ .. py:function:: content.param_docs :param a: First parameter - :param b: The second one + :param b: The second one is different from :p:`a` :param c: And a ``float`` :return: String, of course, it's all *stringly* typed @@ -110,7 +110,7 @@ :param a: First :param c: Third - The ``b`` is not documented, while ``c`` isn't in the signature. + The :p:`b` is not documented, while :p:`c` isn't in the signature. .. py:function:: content.full_docstring :param a: First parameter diff --git a/plugins/m/sphinx.py b/plugins/m/sphinx.py index 49b706e0..9d37e215 100755 --- a/plugins/m/sphinx.py +++ b/plugins/m/sphinx.py @@ -46,6 +46,7 @@ import m.htmlsanity # All those initialized in register() or register_mcss() current_referer_path = None +current_param_names = None module_doc_output = None class_doc_output = None enum_doc_output = None @@ -306,19 +307,31 @@ def ref(name, rawtext, text, lineno, inliner: Inliner, options={}, content=[]): node = nodes.literal(rawtext, target, **_options) return [node], [] -def scope_enter(path, **kwargs): - global current_referer_path +def scope_enter(path, param_names=None, **kwargs): + global current_referer_path, current_param_names current_referer_path += [path] + current_param_names = param_names def scope_exit(path, **kwargs): - global current_referer_path + global current_referer_path, current_param_names assert current_referer_path[-1] == path, "%s %s" % (current_referer_path, path) current_referer_path = current_referer_path[:-1] + current_param_names = None def check_scope_stack_empty(**kwargs): global current_referer_path assert not current_referer_path +def p(name, rawtext, text, lineno, inliner: Inliner, options={}, content=[]): + global current_referer_path, current_param_names + if not current_param_names: + logging.warning("can't reference parameter %s outside of a function scope", text) + elif text not in current_param_names: + logging.warning("parameter %s not found in %s(%s) function signature", text, '.'.join(current_referer_path[-1]), ', '.join(current_param_names)) + + node = nodes.literal(rawtext, text, **options) + return [node], [] + def consume_docstring(type, path: List[str], signature: Optional[str], doc: str) -> str: # Create the directive header based on type if type.name == 'MODULE': @@ -495,6 +508,7 @@ def register_mcss(mcss_settings, module_doc_contents, class_doc_contents, enum_d rst.directives.register_directive('py:data', PyData) rst.roles.register_local_role('ref', ref) + rst.roles.register_local_role('p', p) hooks_pre_scope += [scope_enter] hooks_post_scope += [scope_exit] -- 2.30.2