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_summaryThis module retains summary from the docstring
module submoduleThis submodule has an external summary.
class ClassThis overwrites the docstring for Class.
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