Skip to content

Commit 5b5b263

Browse files
authored
Merge pull request #17 from hakonhagland/sphinx-template-support
Add template support to sphinx_ext_docstrings.py
2 parents 38fb045 + 069db36 commit 5b5b263

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

python/sphinx_docs/src/opm_python_docs/sphinx_ext_docstrings.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,110 @@
1+
"""
2+
OPM Sphinx Documentation Extension
3+
4+
This Sphinx extension automatically generates Python API documentation from JSON docstring files.
5+
It provides custom directives that convert JSON configurations into reStructuredText for Sphinx.
6+
7+
Integration with Sphinx:
8+
- Loaded in docs/conf.py: extensions = ["opm_python_docs.sphinx_ext_docstrings"]
9+
- JSON paths configured in conf.py: opm_simulators_docstrings_path, opm_common_docstrings_path
10+
- Used in .rst files via directives: .. opm_simulators_docstrings:: and .. opm_common_docstrings::
11+
12+
Supported JSON Formats:
13+
1. TEMPLATE FORMAT (New): Uses "simulators", "constructors", "common_methods" with {{name}}/{{class}} expansion
14+
2. FLAT FORMAT (Legacy): Direct key-value pairs with "signature", "doc", "type" fields
15+
16+
Format Detection: Automatic based on presence of "simulators" AND "common_methods" keys
17+
18+
Relationship to generate_docstring_hpp.py:
19+
- This file: JSON → Sphinx documentation (online docs)
20+
- generate_docstring_hpp.py: JSON → C++ headers (pybind11 docstrings)
21+
- Both process the same JSON files but generate different outputs
22+
23+
Usage in .rst files:
24+
.. opm_simulators_docstrings:: # Generates simulator API docs
25+
.. opm_common_docstrings:: # Generates common API docs
26+
"""
27+
128
import json
229
from sphinx.util.nodes import nested_parse_with_titles
330
from docutils.statemachine import ViewList
431
from sphinx.util.docutils import SphinxDirective
532
from docutils import nodes
633

34+
def expand_template(template_dict, simulator_config):
35+
"""Recursively replace {{name}} and {{class}} placeholders"""
36+
if isinstance(template_dict, dict):
37+
result = {}
38+
for key, value in template_dict.items():
39+
result[key] = expand_template(value, simulator_config)
40+
return result
41+
elif isinstance(template_dict, str):
42+
return (template_dict
43+
.replace("{{name}}", simulator_config["name"])
44+
.replace("{{class}}", simulator_config["class"]))
45+
else:
46+
return template_dict
47+
48+
def process_template_docstrings(directive, config):
49+
"""Process template-based docstring configuration"""
50+
result = []
51+
52+
for sim_key, sim_config in config.get("simulators", {}).items():
53+
# Create ViewList for class documentation
54+
rst = ViewList()
55+
signature = f"opm.simulators.{sim_config['name']}"
56+
rst.append(f".. py:class:: {signature}", source="")
57+
rst.append("", source="")
58+
if sim_config.get("doc"):
59+
for line in sim_config["doc"].split('\n'):
60+
rst.append(f" {line}", source="")
61+
rst.append("", source="")
62+
63+
# Process constructors
64+
for constructor_key, constructor_template in config.get("constructors", {}).items():
65+
expanded = expand_template(constructor_template, sim_config)
66+
signature = expanded.get("signature_template", "")
67+
if signature:
68+
# Constructor signatures are methods of the class
69+
rst.append(f" .. py:method:: {signature}", source="")
70+
rst.append("", source="")
71+
doc = expanded.get("doc", "")
72+
if doc:
73+
for line in doc.split('\\n'): # Handle escaped newlines
74+
rst.append(f" {line}", source="")
75+
rst.append("", source="")
76+
77+
# Process methods
78+
for method_name, method_template in config.get("common_methods", {}).items():
79+
expanded = expand_template(method_template, sim_config)
80+
signature = expanded.get("signature_template", "")
81+
if signature:
82+
rst.append(f" .. py:method:: {signature}", source="")
83+
rst.append("", source="")
84+
doc = expanded.get("doc", "")
85+
if doc:
86+
for line in doc.split('\\n'): # Handle escaped newlines
87+
rst.append(f" {line}", source="")
88+
rst.append("", source="")
89+
90+
# Parse all RST content for this simulator
91+
node = nodes.section()
92+
node.document = directive.state.document
93+
nested_parse_with_titles(directive.state, rst, node)
94+
result.extend(node.children)
95+
96+
return result
97+
798
def read_doc_strings(directive, docstrings_path):
899
print(docstrings_path)
9100
with open(docstrings_path, 'r') as file:
10101
docstrings = json.load(file)
102+
103+
# Check if this is template format
104+
if "simulators" in docstrings and "common_methods" in docstrings:
105+
return process_template_docstrings(directive, docstrings)
106+
107+
# Otherwise process as flat format (existing code for backward compatibility)
11108
sorted_docstrings = sorted(docstrings.items(), key=lambda item: item[1].get('signature', item[0]))
12109
result = []
13110
for name, item in sorted_docstrings:
@@ -59,3 +156,13 @@ def setup(app):
59156
app.add_config_value('opm_common_docstrings_path', None, 'env')
60157
app.add_directive("opm_simulators_docstrings", SimulatorsDirective)
61158
app.add_directive("opm_common_docstrings", CommonDirective)
159+
160+
# Return extension metadata for Sphinx (best practice)
161+
# - version: Extension version for debugging/compatibility
162+
# - parallel_read_safe: Enable parallel reading optimization
163+
# - parallel_write_safe: Enable parallel writing optimization
164+
return {
165+
'version': '0.1',
166+
'parallel_read_safe': True,
167+
'parallel_write_safe': True,
168+
}

0 commit comments

Comments
 (0)