-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathplugin.py
183 lines (141 loc) · 5.8 KB
/
plugin.py
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
"""The pytest plugin."""
from __future__ import annotations
from collections.abc import Mapping
import os
from pathlib import Path
from typing import Any, Iterator
from docutils import nodes
from docutils.core import Publisher
import pytest
from sphinx import version_info as sphinx_version_info
from sphinx.environment import BuildEnvironment
from sphinx.testing.util import SphinxTestApp
from .builders import DoctreeBuilder
pytest_plugins = ("sphinx.testing.fixtures",)
@pytest.fixture
def sphinx_doctree(make_app: type[SphinxTestApp], tmp_path: Path):
"""Create a sphinx doctree (before post-transforms)."""
yield CreateDoctree(app_cls=make_app, srcdir=tmp_path / "src")
@pytest.fixture
def sphinx_doctree_no_tr(make_app: type[SphinxTestApp], tmp_path: Path, monkeypatch):
"""Create a sphinx doctree with no transforms."""
def _apply_transforms(self):
pass
monkeypatch.setattr(Publisher, "apply_transforms", _apply_transforms)
yield CreateDoctree(app_cls=make_app, srcdir=tmp_path / "src")
class Doctrees(Mapping):
"""A mapping of doctree names to doctrees."""
def __init__(self, env: BuildEnvironment):
self._env = env
def __getitem__(self, key: str) -> nodes.document:
try:
return self._env.get_doctree(key)
except FileNotFoundError:
raise KeyError(key)
def __iter__(self) -> Iterator[str]:
return iter(self._env.found_docs)
def __len__(self) -> int:
return len(self._env.found_docs)
class AppWrapper:
"""Wrapper for SphinxTestApp to make it easier to use."""
def __init__(self, app: SphinxTestApp) -> None:
self._app = app
@property
def app(self) -> SphinxTestApp:
return self._app
@property
def env(self) -> BuildEnvironment:
assert self._app.env is not None
return self._app.env
@property
def builder(self) -> DoctreeBuilder:
return self._app.builder # type: ignore
def build(self) -> AppWrapper:
self._app.build()
return self
@property
def warnings(self) -> str:
text = self._app._warning.getvalue()
return text.replace(str(self._app.srcdir), "<src>")
@property
def doctrees(self) -> dict[str, nodes.document] | Doctrees:
"""The built doctrees (before post-transforms)."""
try:
return self.builder.doctrees
except AttributeError:
return Doctrees(self.env)
def pformat(
self, docname: str = "index", pop_doc_attrs=("translation_progress",)
) -> str:
"""Return an indented pseudo-XML representation.
The src directory is replaced with <src>, for reproducibility.
:param pop_doc_attrs: Remove these attributes of the doctree node,
before converting to text.
By default, ``translation_progress`` is removed for compatibility
(added in sphinx 7.1).
"""
doctree = self.doctrees[docname].deepcopy()
for attr_name in pop_doc_attrs:
doctree.attributes.pop(attr_name, None)
text = doctree.pformat()
return text.replace(str(self._app.srcdir) + os.sep, "<src>/").rstrip()
def get_resolved_doctree(self, docname: str = "index") -> nodes.document:
"""Return the doctree after post-transforms.
Note only builder agnostic post-transforms will be applied, e.g. not ones for 'html' etc.
"""
doctree = self.doctrees[docname].deepcopy()
self.env.apply_post_transforms(doctree, docname)
# note, this does not resolve toctrees, as in:
# https://github.com/sphinx-doc/sphinx/blob/05a898ecb4ff8e654a053a1ba5131715a4514812/sphinx/environment/__init__.py#L538
return doctree
def get_resolved_pformat(
self, docname: str = "index", pop_doc_attrs=("translation_progress",)
) -> str:
"""Return an indented pseudo-XML representation, after post-transforms.
The src directory is replaced with <src>, for reproducibility.
:param pop_doc_attrs: Remove these attributes of the doctree node,
before converting to text.
By default, ``translation_progress`` is removed for compatibility
(added in sphinx 7.1).
"""
doctree = self.get_resolved_doctree(docname)
for attr_name in pop_doc_attrs:
doctree.attributes.pop(attr_name, None)
text = doctree.pformat()
return text.replace(str(self._app.srcdir) + os.sep, "<src>/").rstrip()
class CreateDoctree:
def __init__(self, app_cls: type[SphinxTestApp], srcdir: Path) -> None:
self._app_cls = app_cls
self.srcdir = srcdir
self.srcdir.mkdir(parents=True, exist_ok=True)
# the test app always sets `confdir = srcdir`, as opposed to None,
# which means a conf.py is required
self.srcdir.joinpath("conf.py").write_text("", encoding="utf8")
self.buildername = "doctree"
self._confoverrides: dict[str, Any] = {}
def set_conf(self, conf: dict[str, Any]) -> CreateDoctree:
self._confoverrides = conf
return self
def __call__(
self,
content: str,
filename: str = "index.rst",
**kwargs,
) -> AppWrapper:
"""Create doctrees for a set of files."""
self.srcdir.joinpath(filename).parent.mkdir(parents=True, exist_ok=True)
self.srcdir.joinpath(filename).write_text(content, encoding="utf8")
srcdir: Any
if sphinx_version_info >= (7, 2):
srcdir = self.srcdir
else:
from sphinx.testing.path import path
srcdir = path(str(self.srcdir))
return AppWrapper(
self._app_cls(
srcdir=srcdir,
buildername=self.buildername,
confoverrides=self._confoverrides,
**kwargs,
)
).build()