From f9f333067bfb0fed8a962840e9afe6be0e2307ce Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 27 Aug 2019 23:54:02 +0200 Subject: [PATCH] documentation/python: unconditionally go through reST for everything. 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 | 93 ++++++++++--------- .../test_python/content/content.html | 22 ++++- .../test_python/content/content/__init__.py | 7 +- documentation/test_python/content/docs.rst | 4 + plugins/m/sphinx.py | 22 ++--- 5 files changed, 85 insertions(+), 63 deletions(-) diff --git a/documentation/python.py b/documentation/python.py index 42d7f15f..761a5e51 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -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

one -# by one -def split_summary_content(doc: str) -> str: - summary, _, content = doc.partition('\n\n') - content = content.strip() - return summary, ('\n'.join(['

{}

'.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

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(['

{}

'.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 diff --git a/documentation/test_python/content/content.html b/documentation/test_python/content/content.html index 9ba7c10a..e834cac8 100644 --- a/documentation/test_python/content/content.html +++ b/documentation/test_python/content/content.html @@ -161,9 +161,25 @@ Detailed docs for this function def content.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.

+ + + + + + + + + + + + + + +
Parameters
aFirst parameter
bSecond
+

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.

diff --git a/documentation/test_python/content/content/__init__.py b/documentation/test_python/content/content/__init__.py index 8f8becbf..339c08c0 100644 --- a/documentation/test_python/content/content/__init__.py +++ b/documentation/test_python/content/content/__init__.py @@ -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 `

`, 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 `

`, but not additionally formatted + or parsed in any way. Like this. """ diff --git a/documentation/test_python/content/docs.rst b/documentation/test_python/content/docs.rst index fbfda272..9a760d6d 100644 --- a/documentation/test_python/content/docs.rst +++ b/documentation/test_python/content/docs.rst @@ -103,6 +103,10 @@ 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`` diff --git a/plugins/m/sphinx.py b/plugins/m/sphinx.py index 03fb7288..75489a94 100755 --- a/plugins/m/sphinx.py +++ b/plugins/m/sphinx.py @@ -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 [] -- 2.30.2