chiark / gitweb /
m.sphinx: more convenient module/class data and properties documentation.
authorVladimír Vondruš <mosra@centrum.cz>
Fri, 30 Aug 2019 14:13:29 +0000 (16:13 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Fri, 30 Aug 2019 16:29:56 +0000 (18:29 +0200)
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
documentation/test_python/content/classes.html
documentation/test_python/content/content.ClassDocumentingItsMembers.html [new file with mode: 0644]
documentation/test_python/content/content.html
documentation/test_python/content/content/__init__.py
documentation/test_python/content/docs.rst
documentation/test_python/test_content.py
plugins/m/sphinx.py

index f0e88dbadfe47353a53582146b80bef2264cca77..c0cf160601380002b5e0763c736f9126fe3da63f 100644 (file)
@@ -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 <name>:` 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 <name>:` option for
+documenting class-level data as well as :rst:`:property <name>:` 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 <name>:` 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 <https://tauday.com/>`_.
+
 `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
index f902a7a64d7a5e4f32fe792210269c5540b69078..876306e5ec1ec12c98f6c49b62fbcb4bbf02708c 100644 (file)
@@ -27,6 +27,7 @@
               <li>module <a href="content.docstring_summary.html" class="m-doc">docstring_summary</a> <span class="m-doc">This module retains summary from the docstring</span></li>
               <li>module <a href="content.submodule.html" class="m-doc">submodule</a> <span class="m-doc">This submodule has an external summary.</span></li>
               <li>class <a href="content.Class.html" class="m-doc">Class</a> <span class="m-doc">This overwrites the docstring for <a class="m-doc" href="content.Class.html">Class</a>.</span></li>
+              <li>class <a href="content.ClassDocumentingItsMembers.html" class="m-doc">ClassDocumentingItsMembers</a> <span class="m-doc">This class documents its members directly in its own directive</span></li>
               <li>class <a href="content.ClassWithSlots.html" class="m-doc">ClassWithSlots</a> <span class="m-doc">This class has slots and those have to be documented externally</span></li>
               <li>class <a href="content.ClassWithSummary.html" class="m-doc">ClassWithSummary</a> <span class="m-doc">This class has summary from the docstring</span></li>
             </ul>
diff --git a/documentation/test_python/content/content.ClassDocumentingItsMembers.html b/documentation/test_python/content/content.ClassDocumentingItsMembers.html
new file mode 100644 (file)
index 0000000..4f629e5
--- /dev/null
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>content.ClassDocumentingItsMembers | My Python Project</title>
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+  <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+  <div class="m-container">
+    <div class="m-row">
+      <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+    </div>
+  </div>
+</nav></header>
+<main><article>
+  <div class="m-container m-container-inflatable">
+    <div class="m-row">
+      <div class="m-col-l-10 m-push-l-1">
+        <h1>
+          <span class="m-breadcrumb"><a href="content.html">content</a>.<wbr/></span>ClassDocumentingItsMembers <span class="m-thin">class</span>
+        </h1>
+        <p>This class documents its members directly in its own directive</p>
+        <div class="m-block m-default">
+          <h3>Contents</h3>
+          <ul>
+            <li>
+              Reference
+              <ul>
+                <li><a href="#properties">Properties</a></li>
+                <li><a href="#data">Data</a></li>
+              </ul>
+            </li>
+          </ul>
+        </div>
+        <section id="properties">
+          <h2><a href="#properties">Properties</a></h2>
+          <dl class="m-doc">
+            <dt id="another">
+              <a href="#another" class="m-doc-self">another</a> <span class="m-label m-flat m-warning">get</span>
+            </dt>
+            <dd>And the other property, documented inside
+<a class="m-doc" href="content.ClassDocumentingItsMembers.html">ClassDocumentingItsMembers</a>!</dd>
+            <dt id="property_documented_in_class">
+              <a href="#property_documented_in_class" class="m-doc-self">property_documented_in_class</a>: float <span class="m-label m-flat m-warning">get</span>
+            </dt>
+            <dd>A property</dd>
+          </dl>
+        </section>
+        <section id="data">
+          <h2><a href="#data">Data</a></h2>
+          <dl class="m-doc">
+            <dt id="ANOTHER">
+              <a href="#ANOTHER" class="m-doc-self">ANOTHER</a> = 1
+            </dt>
+            <dd>And the other, <a class="m-doc" href="content.ClassDocumentingItsMembers.html#ANOTHER">ANOTHER</a>!</dd>
+            <dt id="DATA_DOCUMENTED_IN_CLASS">
+              <a href="#DATA_DOCUMENTED_IN_CLASS" class="m-doc-self">DATA_DOCUMENTED_IN_CLASS</a>: int = 3
+            </dt>
+            <dd>Documentation for the in-class data</dd>
+          </dl>
+        </section>
+      </div>
+    </div>
+  </div>
+</article></main>
+</body>
+</html>
index f603565a31c216648ee48de101807e67b35db67e..1f98a3e94fcd78db90663d4727c05ca3b6f48a47 100644 (file)
@@ -54,6 +54,8 @@ tho.</p>
           <dl class="m-doc">
             <dt>class <a href="content.Class.html" class="m-doc">Class</a></dt>
             <dd>This overwrites the docstring for <a class="m-doc" href="content.Class.html">Class</a>.</dd>
+            <dt>class <a href="content.ClassDocumentingItsMembers.html" class="m-doc">ClassDocumentingItsMembers</a></dt>
+            <dd>This class documents its members directly in its own directive</dd>
             <dt>class <a href="content.ClassWithSlots.html" class="m-doc">ClassWithSlots</a></dt>
             <dd>This class has slots and those have to be documented externally</dd>
             <dt>class <a href="content.ClassWithSummary.html" class="m-doc">ClassWithSummary</a></dt>
@@ -114,10 +116,18 @@ add any detailed block.</dd>
         <section id="data">
           <h2><a href="#data">Data</a></h2>
           <dl class="m-doc">
+            <dt id="ANOTHER_DOCUMENTED_INSIDE_MODULE">
+              <a href="#ANOTHER_DOCUMENTED_INSIDE_MODULE" class="m-doc-self">ANOTHER_DOCUMENTED_INSIDE_MODULE</a> = 3
+            </dt>
+            <dd>In-module summary for another data</dd>
             <dt id="CONSTANT">
               <a href="#CONSTANT" class="m-doc-self">CONSTANT</a>: float = 3.14
             </dt>
             <dd>This is finally a docstring for <a class="m-doc" href="content.html#CONSTANT">CONSTANT</a></dd>
+            <dt id="DATA_DOCUMENTED_INSIDE_MODULE">
+              <a href="#DATA_DOCUMENTED_INSIDE_MODULE" class="m-doc-self">DATA_DOCUMENTED_INSIDE_MODULE</a>: float = 6.28
+            </dt>
+            <dd>In-module summary for the data member</dd>
             <dt>
               <a href="#DATA_WITH_DETAILS" class="m-doc">DATA_WITH_DETAILS</a>: str = &#x27;heyoo&#x27;
             </dt>
index f5b25bb3c34ed683411e8ac3cd1be51021e00f36..b95306e238b141b60f46a8823fd229bce2e44ba4 100644 (file)
@@ -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
index 77d63eec23b0c7b04745a55e5ce2b35947481038..23696220782e5180dcbc8084ed2f15724241e20e 100644 (file)
@@ -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.
 
     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`
index 18cc1caf109ed6bb50961b01fe0af9e095b6402b..be6d9fbcf98fbe9336fc2e4d69899e8352233f2b 100644 (file)
@@ -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):
index 955cc2217e07e4659dc447e5eff718ec6a0bb9a2..20418bac379d34f639a41f53e9c555e4bf7e4c70 100755 (executable)
@@ -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