chiark / gitweb /
documentation/python: unconditionally go through reST for everything.
authorVladimír Vondruš <mosra@centrum.cz>
Tue, 27 Aug 2019 21:54:02 +0000 (23:54 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Wed, 28 Aug 2019 20:34:23 +0000 (22:34 +0200)
Well, of course not forcing reST formatting on plain docstrings -- just
having a single code path because that makes integrating 3rd party
processing easier. Results in absolutely no changes to test files, which
is absolutely amazing.

documentation/python.py
documentation/test_python/content/content.html
documentation/test_python/content/content/__init__.py
documentation/test_python/content/docs.rst
plugins/m/sphinx.py

index 42d7f15f997799c5a712ceab09645d26d0ebd14d..761a5e51031c3018ad3aaad039cbdb80249c93dd 100755 (executable)
@@ -790,54 +790,55 @@ def format_value(state: State, referrer_path: List[str], value: str) -> Optional
     else:
         return None
 
-# Assumes the content is a bunch of raw paragraphs, wrapping them in <p> one
-# by one
-def split_summary_content(doc: str) -> str:
-    summary, _, content = doc.partition('\n\n')
-    content = content.strip()
-    return summary, ('\n'.join(['<p>{}</p>'.format(p) for p in content.split('\n\n')]) if content else None)
-
-def extract_summary(state: State, external_docs, path: List[str], doc: str, *, signature=None) -> str:
-    # Prefer external docs, if available
-    path_str = '.'.join(path)
-    if signature and path_str + signature in external_docs:
-        external_doc_entry = external_docs[path_str + signature]
-    elif path_str in external_docs:
-        external_doc_entry = external_docs[path_str]
-    else:
-        external_doc_entry = None
-    if external_doc_entry and external_doc_entry['summary']:
-        return render_inline_rst(state, external_doc_entry['summary'])
-
-    # some modules (xml.etree) have None as a docstring :(
-    # TODO: render as rst (config option for that)
-    return html.escape(inspect.cleandoc(doc or '').partition('\n\n')[0])
-
-def extract_docs(state: State, external_docs, path: List[str], doc: str, *, signature=None) -> Tuple[str, str]:
+def extract_docs(state: State, external_docs, path: List[str], doc: str, *, signature=None, summary_only=False) -> Tuple[str, str]:
     path_str = '.'.join(path)
     # If function signature is supplied, try that first
     if signature and path_str + signature in external_docs:
-        external_doc_entry = external_docs[path_str + signature]
+        path_signature_str = path_str + signature
+    # If there's already info for all overloads together (or this is not a
+    # function), use that
     elif path_str in external_docs:
-        external_doc_entry = external_docs[path_str]
+        path_signature_str = path_str
+    # Otherwise, create a new entry for the most specific key possible. That
+    # way we can add a different docstring for each overload without them
+    # clashing together.
     else:
-        external_doc_entry = None
-
-    # some modules (xml.etree) have None as a docstring :(
-    # TODO: render as rst (config option for that)
-    summary, content = split_summary_content(html.escape(inspect.cleandoc(doc or '')))
-
-    # Summary. Prefer external docs, if available
-    if external_doc_entry and external_doc_entry['summary']:
-        summary = render_inline_rst(state, external_doc_entry['summary'])
-
-    # Content
-    if external_doc_entry and external_doc_entry['content']:
-        content = render_rst(state, external_doc_entry['content'])
+        path_signature_str = path_str + signature if signature else path_str
+        external_docs[path_signature_str] = {}
+
+    external_doc_entry = external_docs[path_signature_str]
+
+    # If we have parsed summary and content already, don't bother with the
+    # docstring. Otherwise hammer the docs out of it and save those for
+    # later.
+    if external_doc_entry.get('summary') is None or external_doc_entry.get('content') is None:
+        # some modules (xml.etree) have None as a docstring :(
+        summary, _, content = inspect.cleandoc(doc or '').partition('\n\n')
+
+        # Turn both into a raw HTML block so it doesn't get further processed
+        # by reST. For the content, wrap each paragraph in <p> so it looks
+        # acceptable in the output.
+        if summary:
+            summary = html.escape(summary)
+            summary = ".. raw:: html\n\n    " + summary.replace('\n', '\n    ')
+        if content:
+            content = '\n'.join(['<p>{}</p>'.format(p) for p in html.escape(content).split('\n\n')])
+            content = ".. raw:: html\n\n    " + content.replace('\n', '\n    ')
+
+        if external_doc_entry.get('summary') is None:
+            external_doc_entry['summary'] = summary
+        if external_doc_entry.get('content') is None:
+            external_doc_entry['content'] = content
+
+    # Render. This can't be done just once and then cached because e.g. math
+    # rendering needs to ensure each SVG formula has unique IDs on each page.
+    summary = render_inline_rst(state, external_doc_entry['summary'])
+    if summary_only: return summary
+
+    content = render_rst(state, external_doc_entry['content'])
 
     # Mark the docs as used (so it can warn about unused docs at the end)
-    if external_doc_entry: external_doc_entry['used'] = True
-
+    external_doc_entry['used'] = True
     return summary, content
 
 def extract_type(type) -> str:
@@ -973,7 +974,7 @@ def extract_module_doc(state: State, entry: Empty):
     out = Empty()
     out.url = entry.url
     out.name = entry.path[-1]
-    out.summary = extract_summary(state, state.class_docs, entry.path, entry.object.__doc__)
+    out.summary = extract_docs(state, state.class_docs, entry.path, entry.object.__doc__, summary_only=True)
     return out
 
 def extract_class_doc(state: State, entry: Empty):
@@ -982,7 +983,7 @@ def extract_class_doc(state: State, entry: Empty):
     out = Empty()
     out.url = entry.url
     out.name = entry.path[-1]
-    out.summary = extract_summary(state, state.class_docs, entry.path, entry.object.__doc__)
+    out.summary = extract_docs(state, state.class_docs, entry.path, entry.object.__doc__, summary_only=True)
     return out
 
 def extract_enum_doc(state: State, entry: Empty):
@@ -1019,7 +1020,7 @@ def extract_enum_doc(state: State, entry: Empty):
                 docstring = i.__doc__
 
             # TODO: external summary for enum values
-            value.summary = extract_summary(state, {}, [], docstring)
+            value.summary = extract_docs(state, {}, [], docstring, summary_only=True)
 
             if value.summary:
                 out.has_details = True
@@ -1266,7 +1267,7 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
             # Having no parameters documented is okay, having self
             # undocumented as well. But having the rest documented only
             # partially isn't okay.
-            if function_docs['params']:
+            if function_docs.get('params'):
                 param_docs = function_docs['params']
                 used_params = set()
                 for param in out.params:
@@ -1283,7 +1284,7 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
                     if name not in used_params:
                         logging.warning("%s%s documents parameter %s, which isn't in the signature", path_str, signature, name)
 
-            if function_docs['return']:
+            if function_docs.get('return'):
                 out.return_value = render_inline_rst(state, function_docs['return'])
                 out.has_details = True
 
index 9ba7c10aa10c262e79fcb37037d5ac40eef4d072..e834cac82d026abf6667393abd0fcc3d743018d7 100644 (file)
@@ -161,9 +161,25 @@ Detailed docs for this function</div>
               <span class="m-doc-wrap-bumper">def content.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#full_docstring" class="m-doc-self">full_docstring</a>(</span><span class="m-doc-wrap">a, b) -&gt; str</span></span>
             </h3>
             <p>This function has a full docstring.</p>
-<p>It takes one parameter and also another. The details are in two paragraphs,
-each wrapped in its own `&lt;p&gt;`, but not additionally formatted or parsed in any
-way.</p>
+            <table class="m-table m-fullwidth m-flat">
+              <thead>
+                <tr><th colspan="2">Parameters</th></tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <td style="width: 1%">a</td>
+                  <td>First parameter</td>
+                </tr>
+                <tr>
+                  <td>b</td>
+                  <td>Second</td>
+                </tr>
+              </tbody>
+            </table>
+<p>It takes one parameter and also another, which are documented externally,
+but not overwriting the in-source docstring. The details are in two
+paragraphs, each wrapped in its own `&lt;p&gt;`, but not additionally formatted
+or parsed in any way.</p>
 <p>Like this.</p>
           </div></section>
           <section class="m-doc-details" id="function_with_summary"><div>
index 8f8becbf9fc6a67ddc9e94f0ce2d522b504ed163..339c08c0d7331e7ac6df9cebee84b0f2e8aae5bb 100644 (file)
@@ -72,9 +72,10 @@ def param_docs_wrong(a, b):
 def full_docstring(a, b) -> str:
     """This function has a full docstring.
 
-    It takes one parameter and also another. The details are in two paragraphs,
-    each wrapped in its own `<p>`, but not additionally formatted or parsed in any
-    way.
+    It takes one parameter and also another, which are documented externally,
+    but not overwriting the in-source docstring. The details are in two
+    paragraphs, each wrapped in its own `<p>`, but not additionally formatted
+    or parsed in any way.
 
     Like this.
     """
index fbfda272bed3a3cf00d717cc84bb462a5dc05767..9a760d6d6972485c8f188c35cc590f994d5b6949 100644 (file)
 
     The ``b`` is not documented, while ``c`` isn't in the signature.
 
+.. py:function:: content.full_docstring
+    :param a: First parameter
+    :param b: Second
+
 .. py:data:: content.CONSTANT
     :summary: This is finally a docstring for ``content.CONSTANT``
 
index 03fb7288cf10d4a96a00bc7b335052f81599d570..75489a94ff006d53cf22720ee6fd2a98eba4332d 100755 (executable)
@@ -56,7 +56,7 @@ class PyModule(rst.Directive):
 
     def run(self):
         module_doc_output[self.arguments[0]] = {
-            'summary': self.options.get('summary', ''),
+            'summary': self.options.get('summary'),
             'content': '\n'.join(self.content)
         }
         return []
@@ -69,8 +69,8 @@ class PyClass(rst.Directive):
 
     def run(self):
         class_doc_output[self.arguments[0]] = {
-            'summary': self.options.get('summary', ''),
-            'content': '\n'.join(self.content)
+            'summary': self.options.get('summary'),
+            'content': '\n'.join(self.content) if self.content else None
         }
         return []
 
@@ -82,8 +82,8 @@ class PyEnum(rst.Directive):
 
     def run(self):
         enum_doc_output[self.arguments[0]] = {
-            'summary': self.options.get('summary', ''),
-            'content': '\n'.join(self.content)
+            'summary': self.options.get('summary'),
+            'content': '\n'.join(self.content) if self.content else None
         }
         return []
 
@@ -109,10 +109,10 @@ class PyFunction(rst.Directive):
             params[name] = content
 
         function_doc_output[self.arguments[0]] = {
-            'summary': self.options.get('summary', ''),
+            'summary': self.options.get('summary'),
             'params': params,
             'return': self.options.get('return'),
-            'content': '\n'.join(self.content)
+            'content': '\n'.join(self.content) if self.content else None
         }
         return []
 
@@ -124,8 +124,8 @@ class PyProperty(rst.Directive):
 
     def run(self):
         property_doc_output[self.arguments[0]] = {
-            'summary': self.options.get('summary', ''),
-            'content': '\n'.join(self.content)
+            'summary': self.options.get('summary'),
+            'content': '\n'.join(self.content) if self.content else None
         }
         return []
 
@@ -137,8 +137,8 @@ class PyData(rst.Directive):
 
     def run(self):
         data_doc_output[self.arguments[0]] = {
-            'summary': self.options.get('summary', ''),
-            'content': '\n'.join(self.content)
+            'summary': self.options.get('summary'),
+            'content': '\n'.join(self.content) if self.content else None
         }
         return []