From 069db36d9876095de2dd0bbe9a1084e4f26ba218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20H=C3=A6gland?= Date: Fri, 22 Aug 2025 14:11:01 +0200 Subject: [PATCH] Add template support to sphinx_ext_docstrings.py - Detect template format (with 'simulators' and 'common_methods' keys) - Expand templates for all defined simulators - Generate documentation for both BlackOil and GasWater simulators - Maintain backward compatibility for flat JSON format - Process escaped newlines in docstrings correctly --- .../opm_python_docs/sphinx_ext_docstrings.py | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/python/sphinx_docs/src/opm_python_docs/sphinx_ext_docstrings.py b/python/sphinx_docs/src/opm_python_docs/sphinx_ext_docstrings.py index 7b1cfd0..26311c5 100644 --- a/python/sphinx_docs/src/opm_python_docs/sphinx_ext_docstrings.py +++ b/python/sphinx_docs/src/opm_python_docs/sphinx_ext_docstrings.py @@ -1,13 +1,110 @@ +""" +OPM Sphinx Documentation Extension + +This Sphinx extension automatically generates Python API documentation from JSON docstring files. +It provides custom directives that convert JSON configurations into reStructuredText for Sphinx. + +Integration with Sphinx: +- Loaded in docs/conf.py: extensions = ["opm_python_docs.sphinx_ext_docstrings"] +- JSON paths configured in conf.py: opm_simulators_docstrings_path, opm_common_docstrings_path +- Used in .rst files via directives: .. opm_simulators_docstrings:: and .. opm_common_docstrings:: + +Supported JSON Formats: +1. TEMPLATE FORMAT (New): Uses "simulators", "constructors", "common_methods" with {{name}}/{{class}} expansion +2. FLAT FORMAT (Legacy): Direct key-value pairs with "signature", "doc", "type" fields + +Format Detection: Automatic based on presence of "simulators" AND "common_methods" keys + +Relationship to generate_docstring_hpp.py: +- This file: JSON → Sphinx documentation (online docs) +- generate_docstring_hpp.py: JSON → C++ headers (pybind11 docstrings) +- Both process the same JSON files but generate different outputs + +Usage in .rst files: + .. opm_simulators_docstrings:: # Generates simulator API docs + .. opm_common_docstrings:: # Generates common API docs +""" + import json from sphinx.util.nodes import nested_parse_with_titles from docutils.statemachine import ViewList from sphinx.util.docutils import SphinxDirective from docutils import nodes +def expand_template(template_dict, simulator_config): + """Recursively replace {{name}} and {{class}} placeholders""" + if isinstance(template_dict, dict): + result = {} + for key, value in template_dict.items(): + result[key] = expand_template(value, simulator_config) + return result + elif isinstance(template_dict, str): + return (template_dict + .replace("{{name}}", simulator_config["name"]) + .replace("{{class}}", simulator_config["class"])) + else: + return template_dict + +def process_template_docstrings(directive, config): + """Process template-based docstring configuration""" + result = [] + + for sim_key, sim_config in config.get("simulators", {}).items(): + # Create ViewList for class documentation + rst = ViewList() + signature = f"opm.simulators.{sim_config['name']}" + rst.append(f".. py:class:: {signature}", source="") + rst.append("", source="") + if sim_config.get("doc"): + for line in sim_config["doc"].split('\n'): + rst.append(f" {line}", source="") + rst.append("", source="") + + # Process constructors + for constructor_key, constructor_template in config.get("constructors", {}).items(): + expanded = expand_template(constructor_template, sim_config) + signature = expanded.get("signature_template", "") + if signature: + # Constructor signatures are methods of the class + rst.append(f" .. py:method:: {signature}", source="") + rst.append("", source="") + doc = expanded.get("doc", "") + if doc: + for line in doc.split('\\n'): # Handle escaped newlines + rst.append(f" {line}", source="") + rst.append("", source="") + + # Process methods + for method_name, method_template in config.get("common_methods", {}).items(): + expanded = expand_template(method_template, sim_config) + signature = expanded.get("signature_template", "") + if signature: + rst.append(f" .. py:method:: {signature}", source="") + rst.append("", source="") + doc = expanded.get("doc", "") + if doc: + for line in doc.split('\\n'): # Handle escaped newlines + rst.append(f" {line}", source="") + rst.append("", source="") + + # Parse all RST content for this simulator + node = nodes.section() + node.document = directive.state.document + nested_parse_with_titles(directive.state, rst, node) + result.extend(node.children) + + return result + def read_doc_strings(directive, docstrings_path): print(docstrings_path) with open(docstrings_path, 'r') as file: docstrings = json.load(file) + + # Check if this is template format + if "simulators" in docstrings and "common_methods" in docstrings: + return process_template_docstrings(directive, docstrings) + + # Otherwise process as flat format (existing code for backward compatibility) sorted_docstrings = sorted(docstrings.items(), key=lambda item: item[1].get('signature', item[0])) result = [] for name, item in sorted_docstrings: @@ -59,3 +156,13 @@ def setup(app): app.add_config_value('opm_common_docstrings_path', None, 'env') app.add_directive("opm_simulators_docstrings", SimulatorsDirective) app.add_directive("opm_common_docstrings", CommonDirective) + + # Return extension metadata for Sphinx (best practice) + # - version: Extension version for debugging/compatibility + # - parallel_read_safe: Enable parallel reading optimization + # - parallel_write_safe: Enable parallel writing optimization + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + }