forked from OPM/opm-python-documentation
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsphinx_ext_docstrings.py
More file actions
168 lines (141 loc) · 6.81 KB
/
sphinx_ext_docstrings.py
File metadata and controls
168 lines (141 loc) · 6.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""
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:
# Create a ViewList instance for the function signature and docstring
rst = ViewList()
# Check if signature exists and prepend it to the docstring
signature = item.get('signature', '')
item_type = item.get('type', 'method')
signature_line = f".. py:{item_type}:: {signature}" if signature else f".. py:{item_type}:: {name}()"
rst.append(signature_line, source="")
rst.append("", source="")
# Add the docstring text if it exists
docstring = item.get('doc', '')
if docstring:
for line in docstring.split('\n'):
rst.append(f" {line}", source="")
# Create a node that will be populated by nested_parse_with_titles
node = nodes.section()
node.document = directive.state.document
# Parse the rst content
nested_parse_with_titles(directive.state, rst, node)
result.extend(node.children)
return result
class SimulatorsDirective(SphinxDirective):
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
def run(self):
return read_doc_strings(self, self.state.document.settings.env.app.config.opm_simulators_docstrings_path)
class CommonDirective(SphinxDirective):
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
def run(self):
return read_doc_strings(self, self.state.document.settings.env.app.config.opm_common_docstrings_path)
def setup(app):
app.add_config_value('opm_simulators_docstrings_path', None, 'env')
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,
}