|
10 | 10 | # add these directories to sys.path here. If the directory is relative to the |
11 | 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. |
12 | 12 |
|
| 13 | +import re |
13 | 14 | import os |
14 | 15 | import sys |
| 16 | +import shutil |
15 | 17 |
|
16 | 18 | ROOT_DIR = os.path.abspath(os.path.join(__file__, "..", "..")) |
17 | 19 | sys.path.insert(0, ROOT_DIR) |
|
22 | 24 | import wgpu.gui # noqa: E402 |
23 | 25 |
|
24 | 26 |
|
25 | | -# -- Tweak wgpu's docs ------------------------------------------------------- |
| 27 | +# -- Tests ------------------------------------------------------------------- |
26 | 28 |
|
27 | | -# Ensure that all classes are in the docs |
28 | | -with open(os.path.join(ROOT_DIR, "docs", "reference_classes.rst"), "rb") as f: |
29 | | - classes_text = f.read().decode() |
30 | | -for cls_name in wgpu.base.__all__: |
31 | | - expected = f".. autoclass:: {cls_name}" |
32 | | - assert ( |
33 | | - expected in classes_text |
34 | | - ), f"Missing doc entry {cls_name} in reference_classes.rst" |
35 | | - |
36 | | -# Ensure that all classes are references in the alphabetic list, and referenced at least one other time |
37 | | -with open(os.path.join(ROOT_DIR, "docs", "reference_wgpu.rst"), "rb") as f: |
| 29 | +# Ensure that all classes are references in the alphabetic list, |
| 30 | +# and referenced at least one other time as part of the explanatory text. |
| 31 | +with open(os.path.join(ROOT_DIR, "docs", "wgpu.rst"), "rb") as f: |
38 | 32 | wgpu_text = f.read().decode() |
| 33 | + wgpu_lines = [line.strip() for line in wgpu_text.splitlines()] |
39 | 34 | for cls_name in wgpu.base.__all__: |
40 | | - expected1 = f":class:`{cls_name}`" |
41 | | - expected2 = f"* :class:`{cls_name}`" |
42 | | - assert expected2 in wgpu_text, f"Missing doc entry {cls_name} in reference_wgpu.rst" |
43 | 35 | assert ( |
44 | | - wgpu_text.count(expected1) >= 2 |
45 | | - ), f"Need at least one reference to {cls_name} in reference_wgpu.rst" |
| 36 | + f"~{cls_name}" in wgpu_lines |
| 37 | + ), f"Class {cls_name} not listed in class list in wgpu.rst" |
| 38 | + assert ( |
| 39 | + f":class:`{cls_name}`" in wgpu_text |
| 40 | + ), f"Class {cls_name} not referenced in the text in wgpu.rst" |
| 41 | + |
46 | 42 |
|
47 | | -# Make flags and enum appear better in docs |
| 43 | +# -- Hacks to tweak docstrings ----------------------------------------------- |
| 44 | + |
| 45 | +# Make flags and enums appear better in docs |
48 | 46 | wgpu.enums._use_sphinx_repr = True |
49 | 47 | wgpu.flags._use_sphinx_repr = True |
50 | | - |
51 | | -# Also tweak docstrings of classes and their methods |
52 | | -for cls_name, cls in wgpu.base.__dict__.items(): |
53 | | - if cls_name not in wgpu.base.__all__: |
54 | | - continue |
55 | | - |
56 | | - # Change class docstring to include a link to the base class, |
57 | | - # and the class' signature is not shown |
58 | | - base_info = "" |
59 | | - base_classes = [f":class:`.{c.__name__}`" for c in cls.mro()[1:-1]] |
60 | | - if base_classes: |
61 | | - base_info = f" *Subclass of* {', '.join(base_classes)}\n\n" |
62 | | - cls.__doc__ = cls.__name__ + "()\n\n" + base_info + " " + cls.__doc__.lstrip() |
63 | | - # Change docstring of methods that dont have positional arguments |
64 | | - for method in cls.__dict__.values(): |
65 | | - if not (callable(method) and hasattr(method, "__code__")): |
66 | | - continue |
67 | | - if method.__code__.co_argcount == 1 and method.__code__.co_kwonlyargcount > 0: |
68 | | - sig = method.__name__ + "(**parameters)" |
69 | | - method.__doc__ = sig + "\n\n " + method.__doc__.lstrip() |
| 48 | +wgpu.structs._use_sphinx_repr = True |
| 49 | + |
| 50 | +# Build regular expressions to resolve crossrefs |
| 51 | +func_ref_pattern = re.compile(r"\ (`\w+?\(\)`)", re.MULTILINE) |
| 52 | +ob_ref_pattern = re.compile( |
| 53 | + r"\ (`(GPU|gui\.Wgpu|flags\.|enums\.|structs\.)\w+?`)", re.MULTILINE |
| 54 | +) |
| 55 | +argtype_ref_pattern = re.compile( |
| 56 | + r"\(((GPU|gui\.Wgpu|flags\.|enums\.|structs\.)\w+?)\)", re.MULTILINE |
| 57 | +) |
| 58 | + |
| 59 | + |
| 60 | +def resolve_crossrefs(text): |
| 61 | + text = (text or "").lstrip() |
| 62 | + |
| 63 | + # Turn references to functions into a crossref. |
| 64 | + # E.g. `Foo.bar()` |
| 65 | + i2 = 0 |
| 66 | + while True: |
| 67 | + m = func_ref_pattern.search(text, i2) |
| 68 | + if not m: |
| 69 | + break |
| 70 | + i1, i2 = m.start(1), m.end(1) |
| 71 | + ref_indicator = ":func:" |
| 72 | + text = text[:i1] + ref_indicator + text[i1:] |
| 73 | + |
| 74 | + # Turn references to objects (classes, flags, enums, and structs) into a crossref. |
| 75 | + # E.g. `GPUDevice` or `flags.BufferUsage` |
| 76 | + i2 = 0 |
| 77 | + while True: |
| 78 | + m = ob_ref_pattern.search(text, i2) |
| 79 | + if not m: |
| 80 | + break |
| 81 | + i1, i2 = m.start(1), m.end(1) |
| 82 | + prefix = m.group(2) # e.g. GPU or flags. |
| 83 | + ref_indicator = ":obj:" if prefix.lower() == prefix else ":class:" |
| 84 | + text = text[:i1] + ref_indicator + text[i1:] |
| 85 | + |
| 86 | + # Turn function arg types into a crossref. |
| 87 | + # E.g. (GPUDevice) or (flags.BufferUsage) |
| 88 | + i2 = 0 |
| 89 | + while True: |
| 90 | + m = argtype_ref_pattern.search(text) |
| 91 | + if not m: |
| 92 | + break |
| 93 | + i1, i2 = m.start(1), m.end(1) |
| 94 | + ref_indicator = ":obj:" |
| 95 | + text = text[:i1] + ref_indicator + "`" + text[i1:i2] + "`" + text[i2:] |
| 96 | + |
| 97 | + return text |
| 98 | + |
| 99 | + |
| 100 | +# Tweak docstrings of classes and their methods |
| 101 | +for module, hide_class_signature in [(wgpu.base, True), (wgpu.gui, False)]: |
| 102 | + for cls_name in module.__all__: |
| 103 | + cls = getattr(module, cls_name) |
| 104 | + # Class docstring |
| 105 | + docs = resolve_crossrefs(cls.__doc__) |
| 106 | + if hide_class_signature: |
| 107 | + docs = cls.__name__ + "()\n\n " + docs |
| 108 | + cls.__doc__ = docs or None |
| 109 | + # Docstring of methods |
| 110 | + for method in cls.__dict__.values(): |
| 111 | + if callable(method) and hasattr(method, "__code__"): |
| 112 | + docs = resolve_crossrefs(method.__doc__) |
| 113 | + if ( |
| 114 | + method.__code__.co_argcount == 1 |
| 115 | + and method.__code__.co_kwonlyargcount > 0 |
| 116 | + ): |
| 117 | + sig = method.__name__ + "(**parameters)" |
| 118 | + docs = sig + "\n\n " + docs |
| 119 | + method.__doc__ = docs or None |
70 | 120 |
|
71 | 121 |
|
72 | 122 | # -- Project information ----------------------------------------------------- |
73 | 123 |
|
74 | 124 | project = "wgpu-py" |
75 | | -copyright = "2020-2022, Almar Klein, Korijn van Golen" |
| 125 | +copyright = "2020-2023, Almar Klein, Korijn van Golen" |
76 | 126 | author = "Almar Klein, Korijn van Golen" |
77 | 127 | release = wgpu.__version__ |
78 | 128 |
|
|
85 | 135 | extensions = [ |
86 | 136 | "sphinx.ext.autodoc", |
87 | 137 | "sphinx.ext.napoleon", |
| 138 | + "sphinx.ext.autosummary", |
88 | 139 | ] |
89 | 140 |
|
90 | 141 | # Add any paths that contain templates here, relative to this directory. |
91 | 142 | templates_path = ["_templates"] |
92 | 143 |
|
| 144 | +# Just let autosummary produce a new version each time |
| 145 | +shutil.rmtree(os.path.join(os.path.dirname(__file__), "generated"), True) |
| 146 | + |
93 | 147 | # List of patterns, relative to source directory, that match files and |
94 | 148 | # directories to ignore when looking for source files. |
95 | 149 | # This pattern also affects html_static_path and html_extra_path. |
|
102 | 156 |
|
103 | 157 | # The theme to use for HTML and HTML Help pages. See the documentation for |
104 | 158 | # a list of builtin themes. |
105 | | -# |
106 | | -# html_theme = "sphinx_rtd_theme" |
| 159 | + |
| 160 | +if not (os.getenv("READTHEDOCS") or os.getenv("CI")): |
| 161 | + html_theme = "sphinx_rtd_theme" |
107 | 162 |
|
108 | 163 | # Add any paths that contain custom static files (such as style sheets) here, |
109 | 164 | # relative to this directory. They are copied after the builtin static files, |
|
0 commit comments