From 877372f7fd406106f630dddc44a3e1be3d6842ff Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 30 Aug 2019 16:13:29 +0200 Subject: [PATCH] m.sphinx: more convenient module/class data and properties documentation. Sometimes all you need is just a summary. Discovered during a port of a larger project, writing all those fully-qualified .. py:property:: for all members got boring *really* fast. --- doc/plugins/sphinx.rst | 80 ++++++++++++++++++- .../test_python/content/classes.html | 1 + .../content.ClassDocumentingItsMembers.html | 70 ++++++++++++++++ .../test_python/content/content.html | 10 +++ .../test_python/content/content/__init__.py | 15 ++++ documentation/test_python/content/docs.rst | 9 +++ documentation/test_python/test_content.py | 3 +- plugins/m/sphinx.py | 46 +++++++++-- 8 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 documentation/test_python/content/content.ClassDocumentingItsMembers.html diff --git a/doc/plugins/sphinx.rst b/doc/plugins/sphinx.rst index f0e88dba..c0cf1606 100644 --- a/doc/plugins/sphinx.rst +++ b/doc/plugins/sphinx.rst @@ -317,6 +317,52 @@ actual rendered docs. It's however possible to enable INPUT_MODULES = [mymodule] +`Modules`_ +---------- + +The :rst:`.. py:module::` directive documents a Python module. In addition, the +directive supports a :rst:`:data :` option for convenient documenting of +module-level data. The option is equivalent to filling out just a +:rst:`:summary:` of the :rst:`.. py:data::` directive `described below <#data>`_. + +.. code:: rst + + .. py:module:: math + :summary: Common mathematical functions + :data pi: The value of :math:`\pi` + :data tau: The value of :math:`\tau`. Or :math:`2 \pi`. + + This module defines common mathematical functions and constants as + defined by the C standard. + +`Classes`_ +---------- + +Use :rst:`.. py:class::` for documenting classes. Similarly to module docs, +this directive supports an additional :rst:`:data :` option for +documenting class-level data as well as :rst:`:property :` for +properties. Both of those are equivalent to filling out a :rst:`:summary:` of +the :rst:`.. py:data::` / :rst:`.. py:property::` directives `described <#data>`_ +`below <#properties>`_. + +.. code:: rst + + .. py:class:: mymodule.MyContainer + :summary: A container of key/value pairs + :property size: Number of entries in the container + + Provides a key/value storage with :math:`\mathcal{O}(\log{}n)`-complexity + access. + +`Enums`_ +-------- + +Use :rst:`.. py:enum::` for documenting enums. This directive doesn't support +any additional options besides :rst:`:summary:`. + +`Functions`_ +------------ + The :rst:`.. py:function::` directive supports additional options --- :py:`:param :` for documenting parameters and :py:`:return:` for documenting the return value. It's allowed to have either none or all @@ -364,8 +410,40 @@ Example: .. this documentation will be used for all other overloads +`Properties`_ +------------- + +Use :rst:`.. py:property::` for documenting properties. This directive doesn't +support any additional options besides :rst:`:summary:`. For convenience, +properties that have just a summary can be also documented directly in the +enclosing :rst:`.. py:class::` directive `as shown above <#classes>`__. + +.. code:: rst + + .. py:property:: mymodule.MyContainer.size + :summary: Number of entries in the container + + You can also use ``if not container`` for checking if the container is + empty. + +`Data`_ +------- + +Use :rst:`.. py:data::` for documenting module-level and class-level data. This +directive doesn't support any additional options besides :rst:`:summary:`. For +convenience, data that have just a summary can be also documented directly in +the enclosing :rst:`.. py:module::` / :rst:`.. py:class::` directive +`as shown above <#module>`__. + +.. code:: rst + + .. py:data:: math.pi + :summary: The value of :math:`\tau`. Or :math:`2 \pi`. + + They say `pi is wrong `_. + `Using parsed docstrings`_ --------------------------- +========================== By default, docstrings are `treated by the Python doc generator as plain text <{filename}/documentation/python.rst#docstrings>`_ and only externally-supplied docs are parsed. This is done because, for example diff --git a/documentation/test_python/content/classes.html b/documentation/test_python/content/classes.html index f902a7a6..876306e5 100644 --- a/documentation/test_python/content/classes.html +++ b/documentation/test_python/content/classes.html @@ -27,6 +27,7 @@
  • module docstring_summary This module retains summary from the docstring
  • module submodule This submodule has an external summary.
  • class Class This overwrites the docstring for Class.
  • +
  • class ClassDocumentingItsMembers This class documents its members directly in its own directive
  • class ClassWithSlots This class has slots and those have to be documented externally
  • class ClassWithSummary This class has summary from the docstring
  • diff --git a/documentation/test_python/content/content.ClassDocumentingItsMembers.html b/documentation/test_python/content/content.ClassDocumentingItsMembers.html new file mode 100644 index 00000000..4f629e56 --- /dev/null +++ b/documentation/test_python/content/content.ClassDocumentingItsMembers.html @@ -0,0 +1,70 @@ + + + + + content.ClassDocumentingItsMembers | My Python Project + + + + + +
    +
    + + diff --git a/documentation/test_python/content/content.html b/documentation/test_python/content/content.html index f603565a..1f98a3e9 100644 --- a/documentation/test_python/content/content.html +++ b/documentation/test_python/content/content.html @@ -54,6 +54,8 @@ tho.

    class Class
    This overwrites the docstring for Class.
    +
    class ClassDocumentingItsMembers
    +
    This class documents its members directly in its own directive
    class ClassWithSlots
    This class has slots and those have to be documented externally
    class ClassWithSummary
    @@ -114,10 +116,18 @@ add any detailed block.

    Data

    +
    + ANOTHER_DOCUMENTED_INSIDE_MODULE = 3 +
    +
    In-module summary for another data
    CONSTANT: float = 3.14
    This is finally a docstring for CONSTANT
    +
    + DATA_DOCUMENTED_INSIDE_MODULE: float = 6.28 +
    +
    In-module summary for the data member
    DATA_WITH_DETAILS: str = 'heyoo'
    diff --git a/documentation/test_python/content/content/__init__.py b/documentation/test_python/content/content/__init__.py index f5b25bb3..b95306e2 100644 --- a/documentation/test_python/content/content/__init__.py +++ b/documentation/test_python/content/content/__init__.py @@ -48,6 +48,18 @@ class ClassWithSlots: __slots__ = ['hello', 'this_is_a_slot'] +class ClassDocumentingItsMembers: + """This class documents its members directly in its own directive""" + + DATA_DOCUMENTED_IN_CLASS: int = 3 + ANOTHER = 1 + + @property + def property_documented_in_class(self) -> float: pass + + @property + def another(self): pass + class Enum(enum.Enum): """This summary gets ignored""" @@ -95,3 +107,6 @@ CONSTANT: float = 3.14 DATA_WITH_DETAILS: str = 'heyoo' DATA_WITH_DETAILS_BUT_NO_SUMMARY_NEITHER_TYPE = None + +DATA_DOCUMENTED_INSIDE_MODULE: float = 6.28 +ANOTHER_DOCUMENTED_INSIDE_MODULE = 3 diff --git a/documentation/test_python/content/docs.rst b/documentation/test_python/content/docs.rst index 77d63eec..23696220 100644 --- a/documentation/test_python/content/docs.rst +++ b/documentation/test_python/content/docs.rst @@ -10,6 +10,8 @@ .. py:module:: content :summary: This overwrites the docstring for :ref:`content`. + :data DATA_DOCUMENTED_INSIDE_MODULE: In-module summary for the data member + :data ANOTHER_DOCUMENTED_INSIDE_MODULE: In-module summary for another data This is detailed module docs. I kinda *hate* how it needs to be indented, tho. @@ -134,6 +136,13 @@ Why it has to be yelling?! +.. py:class:: content.ClassDocumentingItsMembers + :property property_documented_in_class: A property + :property another: And the other property, documented inside + :ref:`ClassDocumentingItsMembers`! + :data DATA_DOCUMENTED_IN_CLASS: Documentation for the in-class data + :data ANOTHER: And the other, :ref:`ANOTHER`! + .. This should check we handle reST parsing errors gracefully. .. py:function:: content.this_function_has_bad_docs :summary: :nonexistentrole:`summary is all bad` diff --git a/documentation/test_python/test_content.py b/documentation/test_python/test_content.py index 18cc1caf..be6d9fbc 100644 --- a/documentation/test_python/test_content.py +++ b/documentation/test_python/test_content.py @@ -36,8 +36,9 @@ class Content(BaseInspectTestCase): self.assertEqual(*self.actual_expected_contents('content.html')) self.assertEqual(*self.actual_expected_contents('content.docstring_summary.html')) self.assertEqual(*self.actual_expected_contents('content.Class.html')) - self.assertEqual(*self.actual_expected_contents('content.ClassWithSummary.html')) + self.assertEqual(*self.actual_expected_contents('content.ClassDocumentingItsMembers.html')) self.assertEqual(*self.actual_expected_contents('content.ClassWithSlots.html')) + self.assertEqual(*self.actual_expected_contents('content.ClassWithSummary.html')) class ParseDocstrings(BaseInspectTestCase): def test(self): diff --git a/plugins/m/sphinx.py b/plugins/m/sphinx.py index 955cc221..20418bac 100755 --- a/plugins/m/sphinx.py +++ b/plugins/m/sphinx.py @@ -55,32 +55,69 @@ property_doc_output = None data_doc_output = None inventory_filename = None +# List option (allowing multiple arguments). See _docutils_assemble_option_dict +# in python.py for details. +def directives_unchanged_list(argument): + return [directives.unchanged(argument)] + class PyModule(rst.Directive): final_argument_whitespace = True has_content = True required_arguments = 1 - option_spec = {'summary': directives.unchanged} + option_spec = {'summary': directives.unchanged, + 'data': directives_unchanged_list} def run(self): + # Check that data are parsed properly, turn them into a dict. This will + # blow up if the data name is not specified. + properties = {} + data = {} + for name, summary in self.options.get('data', []): + if name in data: raise KeyError("duplicate data {}".format(name)) + data[name] = summary + output = module_doc_output.setdefault(self.arguments[0], {}) if self.options.get('summary'): output['summary'] = self.options['summary'] if self.content: output['content'] = '\n'.join(self.content) + + for name, summary in data.items(): + data_doc_output.setdefault('{}.{}'.format(self.arguments[0], name), {})['summary'] = summary + return [] class PyClass(rst.Directive): final_argument_whitespace = True has_content = True required_arguments = 1 - option_spec = {'summary': directives.unchanged} + option_spec = {'summary': directives.unchanged, + 'property': directives_unchanged_list, + 'data': directives_unchanged_list} def run(self): + # Check that properties and data are parsed properly, turn them into + # dicts. This will blow up if the property / data name is not specified. + properties = {} + for name, summary in self.options.get('property', []): + if name in properties: raise KeyError("duplicate property {}".format(name)) + properties[name] = summary + data = {} + for name, summary in self.options.get('data', []): + if name in data: raise KeyError("duplicate data {}".format(name)) + data[name] = summary + output = class_doc_output.setdefault(self.arguments[0], {}) if self.options.get('summary'): output['summary'] = self.options['summary'] if self.content: output['content'] = '\n'.join(self.content) + + for name, summary in properties.items(): + property_doc_output.setdefault('{}.{}'.format(self.arguments[0], name), {})['summary'] = summary + for name, summary in data.items(): + data_doc_output.setdefault('{}.{}'.format(self.arguments[0], name), {})['summary'] = summary + return [] class PyEnum(rst.Directive): @@ -97,11 +134,6 @@ class PyEnum(rst.Directive): output['content'] = '\n'.join(self.content) return [] -# List option (allowing multiple arguments). See _docutils_assemble_option_dict -# in python.py for details. -def directives_unchanged_list(argument): - return [directives.unchanged(argument)] - class PyFunction(rst.Directive): final_argument_whitespace = True has_content = True -- 2.30.2