chiark / gitweb /
documentation/python: dying on each error isn't nice.
authorVladimír Vondruš <mosra@centrum.cz>
Wed, 28 Aug 2019 17:43:18 +0000 (19:43 +0200)
committerVladimír Vondruš <mosra@centrum.cz>
Fri, 30 Aug 2019 14:47:58 +0000 (16:47 +0200)
documentation/python.py
documentation/test_python/content/content.html
documentation/test_python/content/content/__init__.py
documentation/test_python/content/docs.rst
documentation/test_python/content_parse_docstrings/content_parse_docstrings.html
documentation/test_python/content_parse_docstrings/content_parse_docstrings.py

index 1044c6deb41db3b9470f71a3b5cbbba89a997235..0322a05dad3f9be2197126da1af6e29a740381ea 100755 (executable)
@@ -791,6 +791,9 @@ def format_value(state: State, referrer_path: List[str], value: str) -> Optional
     else:
         return None
 
+def prettify_multiline_error(error: str) -> str:
+    return ' | {}\n'.format(error.replace('\n', '\n | '))
+
 def extract_docs(state: State, external_docs, type: EntryType, 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
@@ -829,24 +832,29 @@ def extract_docs(state: State, external_docs, type: EntryType, path: List[str],
             # this one after another; stopping once there's nothing left. If
             # nothing left, the populated entries should be non-None.
             for hook in state.hooks_docstring:
-                doc = hook(
-                    type=type,
-                    path=path,
-                    signature=signature,
-                    doc=doc)
-
-                # The hook could have replaced the entry with a new dict
-                # instance, fetch it again to avoid looking at stale data below
-                external_doc_entry = external_docs[path_signature_str]
-
-                if not doc:
-                    # Assuming the doc were non-empty on input, if those are
-                    # empty on output, the hook should be filling both summary
-                    # and content to non-None values (so, in the worst case,
-                    # an empty string)
-                    assert external_doc_entry['summary'] is not None
-                    assert external_doc_entry['content'] is not None
-                    break
+                try:
+                    doc = hook(
+                        type=type,
+                        path=path,
+                        signature=signature,
+                        doc=doc)
+
+                    # The hook could have replaced the entry with a new dict
+                    # instance, fetch it again to avoid looking at stale data below
+                    external_doc_entry = external_docs[path_signature_str]
+
+                    if not doc:
+                        # Assuming the doc were non-empty on input, if those are
+                        # empty on output, the hook should be filling both summary
+                        # and content to non-None values (so, in the worst case,
+                        # an empty string)
+                        assert external_doc_entry['summary'] is not None
+                        assert external_doc_entry['content'] is not None
+                        break
+
+                except docutils.utils.SystemMessage:
+                    logging.error("Failed to process a docstring for %s, ignoring:\n%s", path_signature_str, prettify_multiline_error(doc))
+                    doc = ''
 
             # If there's still something left after the hooks (or there are no
             # hooks), process it as a plain unformatted text.
@@ -872,10 +880,10 @@ def extract_docs(state: State, external_docs, type: EntryType, path: List[str],
             docutils.utils.extract_options = prev_extract_options
             docutils.utils.assemble_option_dict = prev_assemble_option_dict
 
-        # We ain't got nothing. If there isn't anything supplied externally,
-        # set summary / content to an empty string so this branch isn't entered
-        # again.
-        else:
+        # We ain't got nothing (or the above parse failed). If there isn't
+        # anything supplied externally, set summary / content to an empty
+        # string so this branch isn't entered again.
+        if not doc:
             if external_doc_entry.get('summary') is None:
                 external_doc_entry['summary'] = ''
             if external_doc_entry.get('content') is None:
@@ -883,10 +891,19 @@ def extract_docs(state: State, external_docs, type: EntryType, path: List[str],
 
     # 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'])
+    try:
+        summary = render_inline_rst(state, external_doc_entry['summary'])
+    except docutils.utils.SystemMessage:
+        logging.error("Failed to process summary for %s, ignoring:\n%s", path_signature_str, prettify_multiline_error(external_doc_entry['summary']))
+        summary = ''
+
     if summary_only: return summary
 
-    content = render_rst(state, external_doc_entry['content'])
+    try:
+        content = render_rst(state, external_doc_entry['content'])
+    except docutils.utils.SystemMessage:
+        logging.error("Failed to process content for %s, ignoring:\n%s", path_signature_str, prettify_multiline_error(external_doc_entry['content']))
+        content = ''
 
     # Mark the docs as used (so it can warn about unused docs at the end)
     external_doc_entry['used'] = True
@@ -1326,7 +1343,11 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
                         if param.name != 'self':
                             logging.warning("%s%s parameter %s is not documented", path_str, signature, param.name)
                         continue
-                    param.content = render_inline_rst(state, param_docs[param.name])
+                    try:
+                        param.content = render_inline_rst(state, param_docs[param.name])
+                    except docutils.utils.SystemMessage:
+                        logging.error("Failed to process doc for %s param %s, ignoring:\n%s", path_str, param.name,  prettify_multiline_error(param_docs[param.name]))
+                        param.content = ''
                     used_params.add(param.name)
                     out.has_param_details = True
                     out.has_details = True
@@ -1336,7 +1357,11 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
                         logging.warning("%s%s documents parameter %s, which isn't in the signature", path_str, signature, name)
 
             if function_docs.get('return'):
-                out.return_value = render_inline_rst(state, function_docs['return'])
+                try:
+                    out.return_value = render_inline_rst(state, function_docs['return'])
+                except docutils.utils.SystemMessage:
+                    logging.error("Failed to process return doc for %s, ignoring:\n%s", path_str, prettify_multiline_error(function_docs['return']))
+                    out.return_value = ''
                 out.has_details = True
 
         if not state.config['SEARCH_DISABLED']:
index 3fb124db47fbc8c648390232e03c83cd682b9fd7..35622ede6729090535c6aa742cee5d6454b78403 100644 (file)
@@ -103,6 +103,10 @@ doesn't add any detailed block.</dd>
               <span class="m-doc-wrap-bumper">def <a href="#param_docs_wrong" class="m-doc">param_docs_wrong</a>(</span><span class="m-doc-wrap">a, b)</span>
             </dt>
             <dd>Should give warnings</dd>
+            <dt>
+              <span class="m-doc-wrap-bumper">def <a href="#this_function_has_bad_docs" class="m-doc">this_function_has_bad_docs</a>(</span><span class="m-doc-wrap">a, b)</span>
+            </dt>
+            <dd></dd>
           </dl>
         </section>
         <section id="data">
@@ -247,6 +251,26 @@ or parsed in any way.</p>
             </table>
 <p>The <code>b</code> is not documented, while <code>c</code> isn't in the signature.</p>
           </div></section>
+          <section class="m-doc-details" id="this_function_has_bad_docs"><div>
+            <h3>
+              <span class="m-doc-wrap-bumper">def content.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#this_function_has_bad_docs" class="m-doc-self">this_function_has_bad_docs</a>(</span><span class="m-doc-wrap">a, b)</span></span>
+            </h3>
+            <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></td>
+                </tr>
+                <tr>
+                  <td>b</td>
+                  <td></td>
+                </tr>
+              </tbody>
+            </table>
+          </div></section>
         </section>
         <section>
           <h2>Data documentation</h2>
index 9850d0b722146b4a6997e06822b01895ea90d6ce..46691793469ef91302a9158ce81cdf945e7fc79b 100644 (file)
@@ -80,6 +80,11 @@ def full_docstring(a, b) -> str:
     Like this.
     """
 
+# This should check we handle reST parsing errors in external docs gracefully.
+# Will probably look extra weird in the output tho, but that's okay -- it's an
+# error after all.
+def this_function_has_bad_docs(a, b): pass
+
 CONSTANT: float = 3.14
 
 DATA_WITH_DETAILS: str = 'heyoo'
index 76d3d884f298f8b929acfb01e62e3a969972850a..902e5f7018e2d4f61662811df545065b0b1911ea 100644 (file)
 
     Why it has to be yelling?!
 
+.. This should check we handle reST parsing errors gracefully.
+.. py:function:: content.this_function_has_bad_docs
+    :summary: :nonexistentrole:`summary is all bad`
+    :param a: :nonexistentrole:`param docs also blow`
+    :return: :nonexistentrole:`return is terrible`
+
+    :nonexistentrole:`this too`
+
 .. py:module:: thismoduledoesnotexist
     :summary: This docs get unused and produce a warning
 
index edfdf553b14ee4118472c89ba136ff0e3cff3974..e3d6acb8b74c045e8683de35fdfb2acfaf9fb8d2 100644 (file)
@@ -70,6 +70,10 @@ With a multi-line summary.</dd>
               <span class="m-doc-wrap-bumper">def <a href="#summary_only" class="m-doc-self">summary_only</a>(</span><span class="m-doc-wrap">)</span>
             </dt>
             <dd>This is just a summary.</dd>
+            <dt id="this_function_has_bad_docs">
+              <span class="m-doc-wrap-bumper">def <a href="#this_function_has_bad_docs" class="m-doc-self">this_function_has_bad_docs</a>(</span><span class="m-doc-wrap">a, b) -&gt; str</span>
+            </dt>
+            <dd></dd>
           </dl>
         </section>
         <section>
index 24600dc0262c491b0817042dfaace7e30bb2b6f0..021628db2131bdb320a529da610e5cb02df969f7 100644 (file)
@@ -40,3 +40,14 @@ def empty_docstring(): pass
 
 def summary_only():
     """This is just a summary."""
+
+# This should check we handle reST parsing errors gracefully. Will probably
+# look extra weird in the output tho, but that's okay -- it's an error after
+# all.
+def this_function_has_bad_docs(a, b) -> str:
+    """This function has bad docs. It's freaking terrible.
+        Yes.
+            Really.
+
+    :broken: yes
+    """