-
Notifications
You must be signed in to change notification settings - Fork 145
Expand file tree
/
Copy pathconf.py
More file actions
345 lines (277 loc) · 11.4 KB
/
conf.py
File metadata and controls
345 lines (277 loc) · 11.4 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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
import doctest
import os
import re
import shutil
import subprocess
from pathlib import Path
import hawkmoth.docstring
from sphinx.util import logging
log = logging.getLogger("vortex.docs.conf")
# Configuration file for the Sphinx documentation builder.
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = "Vortex"
copyright = "The Vortex contributors"
author = "Vortex contributors"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"breathe", # C++ API (Doxygen -> Sphinx bridge)
"hawkmoth", # C API
"myst_parser", # Markdown support
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"sphinx.ext.doctest",
"sphinx.ext.intersphinx",
"sphinx.ext.napoleon",
"sphinx_copybutton",
"sphinx_design",
"sphinx_inline_tabs",
"sphinxcontrib.bibtex",
"sphinxcontrib.mermaid",
"sphinxext.opengraph",
]
templates_path = ["_templates"]
html_show_sourcelink = False
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "README.md"]
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"pyarrow": ("https://arrow.apache.org/docs", None),
"pandas": ("https://pandas.pydata.org/pandas-docs/version/2.3/", None),
"numpy": ("https://numpy.org/doc/stable", None),
"polars": ("https://docs.pola.rs/api/python/stable", "polars.objects.inv"),
}
git_root = Path(__file__).parent.parent
nitpicky = True # ensures all :class:, :obj:, etc. links are valid
nitpick_ignore = []
doctest_global_setup = "import pyarrow; import vortex; import vortex as vx; import random; random.seed(a=0)"
doctest_default_flags = (
doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL | doctest.DONT_ACCEPT_TRUE_FOR_1 | doctest.NORMALIZE_WHITESPACE
)
# -- Options for MyST Parser -------------------------------------------------
myst_enable_extensions = [
"colon_fence", # Use ::: for Sphinx directives
]
myst_heading_anchors = 3
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "pydata_sphinx_theme"
html_static_path = ["_static"]
html_css_files = ["style.css"]
html_favicon = "_static/vortex_logo.svg" # relative to _static/
# -- Options for PyData Sphinx Theme ----------------------------------------
html_theme_options = {
"logo": {
"image_light": "_static/vortex_logo.svg",
"image_dark": "_static/vortex_logo_dark_theme.svg",
},
"github_url": "https://github.com/vortex-data/vortex",
"icon_links": [
{
"name": "PyPI",
"url": "https://pypi.org/project/vortex-data",
"icon": "fa-brands fa-python",
},
{
"name": "Crates.io",
"url": "https://crates.io/crates/vortex",
"icon": "fa-brands fa-rust",
},
],
"header_links_before_dropdown": 7,
"navbar_align": "left",
"show_nav_level": 2,
"navigation_depth": 3,
"show_toc_level": 2,
}
# -- Options for OpenGraph ---------------------------------------------------
ogp_site_url = "https://docs.vortex.dev"
ogp_image = "https://docs.vortex.dev/_static/vortex_logo.svg"
# -- Options for Sphinx BibTEX -------------------------------------------
bibtex_bibfiles = ["references.bib"]
# -- Options for Breathe C++ API gen ------------------------------------
_doxygen_xml_dir = str(Path(__file__).parent / "_build" / "doxygen-cpp" / "xml")
os.makedirs(os.path.dirname(_doxygen_xml_dir), exist_ok=True)
if not shutil.which("doxygen"):
raise RuntimeError("doxygen is required to build the docs but was not found on PATH")
subprocess.run(["doxygen", "Doxyfile.cpp"], cwd=Path(__file__).parent, check=True)
breathe_projects = {"vortex-cpp": _doxygen_xml_dir}
breathe_default_project = "vortex-cpp"
# C++ types from cxx bridge and standard library that Sphinx cannot resolve.
nitpick_ignore += [
("cpp:identifier", t)
for t in [
"vortex",
"rust",
"ffi",
"uint8_t",
"uint16_t",
"uint32_t",
"uint64_t",
"int8_t",
"int16_t",
"int32_t",
"int64_t",
"size_t",
"std::size_t",
]
]
nitpick_ignore_regex = [
# cxx bridge internals that will never be resolvable in Sphinx.
(r"cpp:identifier", r"rust::.*"),
(r"cpp:identifier", r"ffi::.*"),
# Doxygen file-level labels (e.g. "dtype_8hpp") that we don't generate pages for.
(r"ref", r".*_8hpp"),
]
# -- Options for hawkmoth C API gen ----------------------------
hawkmoth_root = str(git_root / "vortex-ffi/cinclude")
# C types that aren't keywords are not found, so we need to ignore them.
nitpick_ignore += [
("c:identifier", "bool"),
("c:identifier", "usize_t"),
("c:identifier", "size_t"),
("c:identifier", "uint64_t"),
("c:identifier", "int64_t"),
("c:identifier", "uint32_t"),
("c:identifier", "int32_t"),
("c:identifier", "uint16_t"),
("c:identifier", "int16_t"),
("c:identifier", "uint8_t"),
("c:identifier", "int8_t"),
]
hawkmoth_transform_default = "c_to_rust"
# Track the hawkmoth references so we can warn if they are not all registered!
C_DOCS: set[str] | None = None
def _replace_rust_references(app, lines, transform, options):
"""Replace Rust references with C equivalents in hawkmoth docstrings.
See: https://hawkmoth.readthedocs.io/en/stable/extending.html#event-hawkmoth-process-docstring
"""
if transform != "c_to_rust":
# Not for us!
return
import sys
# This is one of my finest hacks...
# Hawkmoth doesn't expose type information to us. So we grab it from the caller's stack frame locals.
stack_frame = sys._getframe(6)
docs: hawkmoth.docstring.RootDocstring = stack_frame.f_locals["root"]
global C_DOCS
if C_DOCS is None:
C_DOCS = set(
d._name
for d in docs.walk(
recurse=False, # Ignore e.g. enum members
filter_types=(
hawkmoth.docstring.FunctionDocstring,
hawkmoth.docstring.EnumDocstring,
hawkmoth.docstring.UnionDocstring,
hawkmoth.docstring.StructDocstring,
),
)
)
# Remove the current docstring from the set of C docs
slf = stack_frame.f_locals["self"]
C_DOCS.discard(slf.arguments[0])
# Pattern to match [`crate::path::to::function`]
pattern = r"\[`([^:]+::)*?(vx_[^`]+)`\]"
def replace_match(match):
# Extract the function name (already starts with vx_)
# TODO(ngates): detect if the reference is a function or a type
func_name = match.group(2)
refs = list(docs.walk(filter_names=[func_name]))
if not refs:
# If we can't find the function, return the original match without a reference
return func_name
ref = refs[0]
if isinstance(ref, hawkmoth.docstring.FunctionDocstring):
# If it's a function, return the C identifier
return f":c:func:`{func_name}`"
elif isinstance(ref, hawkmoth.docstring.EnumDocstring):
# If it's an enum, return the C identifier
return f":c:type:`{func_name}`"
elif isinstance(ref, hawkmoth.docstring.TypedefDocstring):
# If it's a typedef, return the C identifier
return f":c:type:`{func_name}`"
elif isinstance(ref, hawkmoth.docstring.StructDocstring):
# If it's a typedef, return the C identifier
return f":c:type:`{func_name}`"
else:
return func_name
for i, line in enumerate(lines):
lines[i] = re.sub(pattern, replace_match, line)
def _post_process(app, builder):
"""Post-process the documentation after writing."""
global C_DOCS
if C_DOCS:
# TODO(ngates): enable this one we've cleaned up the entire C API.
# log.warning("Some C references were not found: %s", ", ".join(sorted(C_DOCS)))
C_DOCS = None # Reset for next build
# Most tools change their table formatting based on the perceived number of columns. Most will
# obey the COLUMNS environment variable (because they use `shutil.get_terminal_size()`), but
# some COUGH polars COUGH do not.
os.environ["COLUMNS"] = "80"
# https://github.com/pola-rs/polars/blob/8a55acce8bb822c549861c371b6d48dee6c3379f/crates/polars-core/src/fmt.rs#L720
os.environ["POLARS_TABLE_WIDTH"] = "80"
def _convert_python_fenced_blocks_from_rust_to_valid_reST_blocks(app, what, name, obj, options, lines: list[str]):
"""Remove Markdown-style code fences from Python docs written in Rust.
We would like `cargo test` to Just Work (TM). Unfortunately, by default, it executes any
code-block in any docstring even though we intend those docs to be *Python* doc tests.
For example, the following is interpreted by Rust as Rust code (which it will try to doctest):
/// >>> 1 + 1
/// 3
fn foo() {
}
What syntax can we use to communicate to Rust "This is not Rust code" but communicate to Python
"This is Python code"? The following appears as executable code to both, so it does not work:
/// .. code-block:: python
///
/// >>> 1 + 1
/// 3
fn foo() {
}
This does not appear to work unless we wrap all the code in braces or a function, which makes it
not valid Python:
/// #[no_run]
/// >>> 1 + 1
/// 3
The following is executed by neither language and does not render properly (because it is not
valid reStructured Text):
/// ```python
/// >>> 1 + 1
/// 3
/// ```
Okay, so, our solution is to just adopt the last option and explicitly remove the code fences
when we parse docstrings in Sphinx.
"""
in_block = False
for i, line in enumerate(lines):
if line == "```python":
lines[i] = ""
in_block = True
elif in_block and line == "```":
lines[i] = ""
in_block = False
def _resolve_breathe_cpp_references(app, env, node, contnode):
"""Resolve relative C++ references emitted by Breathe.
Breathe emits cross-namespace parameter types with relative qualifiers (e.g. ``scalar::Scalar``
instead of ``vortex::scalar::Scalar``). This handler intercepts unresolved references and
re-resolves them under the ``vortex::`` namespace.
"""
if node.get("refdomain") != "cpp" or node.get("reftype") != "identifier":
return None
target = node.get("reftarget", "")
if not target or target.startswith("vortex::"):
return None
cpp_domain = env.get_domain("cpp")
# Try resolving with the vortex:: prefix.
node = node.deepcopy()
node["reftarget"] = f"vortex::{target}"
return cpp_domain.resolve_xref(
env, node.get("refdoc", ""), app.builder, "identifier", node["reftarget"], node, contnode
)
def setup(app):
app.connect("hawkmoth-process-docstring", _replace_rust_references)
app.connect("write-started", _post_process)
app.connect("autodoc-process-docstring", _convert_python_fenced_blocks_from_rust_to_valid_reST_blocks)
app.connect("missing-reference", _resolve_breathe_cpp_references)