sphinx-external-toc-strict 1.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sphinx_external_toc_strict/__init__.py +47 -0
- sphinx_external_toc_strict/__init__.pyi +12 -0
- sphinx_external_toc_strict/_compat.py +265 -0
- sphinx_external_toc_strict/_version.py +16 -0
- sphinx_external_toc_strict/_version.pyi +13 -0
- sphinx_external_toc_strict/api.py +424 -0
- sphinx_external_toc_strict/api.pyi +105 -0
- sphinx_external_toc_strict/cli.py +251 -0
- sphinx_external_toc_strict/cli.pyi +23 -0
- sphinx_external_toc_strict/constants.py +122 -0
- sphinx_external_toc_strict/constants.pyi +31 -0
- sphinx_external_toc_strict/events.py +477 -0
- sphinx_external_toc_strict/events.pyi +47 -0
- sphinx_external_toc_strict/exceptions.py +18 -0
- sphinx_external_toc_strict/exceptions.pyi +10 -0
- sphinx_external_toc_strict/filename_suffix.py +194 -0
- sphinx_external_toc_strict/filename_suffix.pyi +20 -0
- sphinx_external_toc_strict/parsing_shared.py +340 -0
- sphinx_external_toc_strict/parsing_shared.pyi +68 -0
- sphinx_external_toc_strict/parsing_strictyaml.py +593 -0
- sphinx_external_toc_strict/parsing_strictyaml.pyi +65 -0
- sphinx_external_toc_strict/pep518_read.py +241 -0
- sphinx_external_toc_strict/pep518_read.pyi +29 -0
- sphinx_external_toc_strict/py.typed +1 -0
- sphinx_external_toc_strict/tools_strictyaml.py +578 -0
- sphinx_external_toc_strict/tools_strictyaml.pyi +60 -0
- sphinx_external_toc_strict/version_semantic.py +331 -0
- sphinx_external_toc_strict/version_semantic.pyi +18 -0
- sphinx_external_toc_strict-1.1.1.dist-info/LICENSE +21 -0
- sphinx_external_toc_strict-1.1.1.dist-info/METADATA +820 -0
- sphinx_external_toc_strict-1.1.1.dist-info/NOTICE.txt +14 -0
- sphinx_external_toc_strict-1.1.1.dist-info/RECORD +35 -0
- sphinx_external_toc_strict-1.1.1.dist-info/WHEEL +5 -0
- sphinx_external_toc_strict-1.1.1.dist-info/entry_points.txt +5 -0
- sphinx_external_toc_strict-1.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""A sphinx extension that allows the project toctree to be defined in a single file.
|
|
2
|
+
|
|
3
|
+
.. py:data:: __version__
|
|
4
|
+
:type: str
|
|
5
|
+
|
|
6
|
+
Hardcoded version in x.y.z format. Not semantic version. Crying out
|
|
7
|
+
to add setuptools-scm suppport
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .constants import __version_app as __version__
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def setup(app):
|
|
15
|
+
"""Initialize the Sphinx extension
|
|
16
|
+
|
|
17
|
+
:param app: Sphinx instance
|
|
18
|
+
:type app: sphinx.application.Sphinx
|
|
19
|
+
:returns:
|
|
20
|
+
|
|
21
|
+
dict containing version and whether safe to run in parallel.
|
|
22
|
+
Keys: version, parallel_read_safe
|
|
23
|
+
|
|
24
|
+
:rtype: dict[str, str | bool]
|
|
25
|
+
"""
|
|
26
|
+
from .events import (
|
|
27
|
+
InsertToctrees,
|
|
28
|
+
TableofContents,
|
|
29
|
+
add_changed_toctrees,
|
|
30
|
+
ensure_index_file,
|
|
31
|
+
parse_toc_to_env,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# variables
|
|
35
|
+
app.add_config_value("external_toc_path", "_toc.yml", "env")
|
|
36
|
+
app.add_config_value("external_toc_exclude_missing", False, "env")
|
|
37
|
+
|
|
38
|
+
# Note: this needs to occur after merge_source_suffix event (priority 800)
|
|
39
|
+
# this cannot be a builder-inited event, since if we change the master_doc
|
|
40
|
+
# it will always mark the config as changed in the env setup and re-build everything
|
|
41
|
+
app.connect("config-inited", parse_toc_to_env, priority=900)
|
|
42
|
+
app.connect("env-get-outdated", add_changed_toctrees)
|
|
43
|
+
app.add_directive("tableofcontents", TableofContents)
|
|
44
|
+
app.add_transform(InsertToctrees)
|
|
45
|
+
app.connect("build-finished", ensure_index_file)
|
|
46
|
+
|
|
47
|
+
return {"version": __version__, "parallel_read_safe": True}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from sphinx.application import Sphinx
|
|
6
|
+
|
|
7
|
+
if sys.version_info >= (3, 8): # pragma: no cover
|
|
8
|
+
from typing import Final
|
|
9
|
+
else: # pragma: no cover
|
|
10
|
+
from typing_extensions import Final
|
|
11
|
+
|
|
12
|
+
def setup(app: Sphinx) -> dict[str, str | bool]: ...
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dataclasses dynamically construct tools and Validators
|
|
3
|
+
|
|
4
|
+
Compatibility for using dataclasses instead of attrs.
|
|
5
|
+
|
|
6
|
+
**But** a dynamic dataclasses.dataclasses with dynamic
|
|
7
|
+
dataclasses.Field **cannot** be static type checked!
|
|
8
|
+
|
|
9
|
+
dataclasses.Field is for internal use only and is not a type
|
|
10
|
+
|
|
11
|
+
dataclasses.dataclass is not a valid type. Create a Protocol?
|
|
12
|
+
|
|
13
|
+
.. py:data:: UnionAB
|
|
14
|
+
:type: typing.TypeAlias
|
|
15
|
+
:value: typing.Union[typing.Any, collections.abc.Sequence[typing.Any]]
|
|
16
|
+
|
|
17
|
+
:py:func:`isinstance` accepts a type or a tuple of types
|
|
18
|
+
|
|
19
|
+
.. py:data:: ValidatorType
|
|
20
|
+
:type: typing.TypeAlias
|
|
21
|
+
:value: Callable[[Any, dc.Field[Any], UnionAB], None]
|
|
22
|
+
|
|
23
|
+
Reinventing the wheel. :pypi_org:`attrs` has built-in validators.
|
|
24
|
+
Evidently dataclasses doesn't
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import dataclasses as dc
|
|
31
|
+
import re
|
|
32
|
+
import sys
|
|
33
|
+
from typing import (
|
|
34
|
+
Any,
|
|
35
|
+
Callable,
|
|
36
|
+
Pattern,
|
|
37
|
+
Protocol,
|
|
38
|
+
Union,
|
|
39
|
+
cast,
|
|
40
|
+
runtime_checkable,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
from docutils.nodes import Element
|
|
44
|
+
|
|
45
|
+
if sys.version_info >= (3, 9): # pragma: no cover
|
|
46
|
+
from collections.abc import Sequence
|
|
47
|
+
else: # pragma: no cover
|
|
48
|
+
from typing import Sequence
|
|
49
|
+
|
|
50
|
+
if sys.version_info >= (3, 10): # pragma: no cover
|
|
51
|
+
from typing import TypeAlias
|
|
52
|
+
else: # pragma: no cover
|
|
53
|
+
from typing_extensions import TypeAlias
|
|
54
|
+
|
|
55
|
+
# Use dataclasses slots True for reduced memory usage and performance gain
|
|
56
|
+
if sys.version_info >= (3, 10): # pragma: no cover
|
|
57
|
+
DC_SLOTS: dict[str, bool] = {"slots": True}
|
|
58
|
+
else: # pragma: no cover
|
|
59
|
+
DC_SLOTS: dict[str, bool] = {}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# https://stackoverflow.com/questions/54668000/type-hint-for-an-instance-of-a-non-specific-dataclass
|
|
63
|
+
@runtime_checkable
|
|
64
|
+
@dc.dataclass
|
|
65
|
+
class DataclassProtocol(Protocol):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def field(**kwargs: Any) -> Any:
|
|
70
|
+
"""dataclasses have fields. When dynamically creating a dataclass
|
|
71
|
+
the fields must also be dynamically created
|
|
72
|
+
|
|
73
|
+
:param kwargs: keyword/value pairs dict passed to :py:func:`dataclasses.field`
|
|
74
|
+
:type kwargs: typing.Any
|
|
75
|
+
:returns: A dynamically created dataclasses field
|
|
76
|
+
:rtype: typing.Any
|
|
77
|
+
"""
|
|
78
|
+
if sys.version_info < (3, 10):
|
|
79
|
+
kwargs.pop("kw_only", None)
|
|
80
|
+
else: # pragma: no cover
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
if "validator" in kwargs:
|
|
84
|
+
kwargs.setdefault("metadata", {})["validator"] = kwargs.pop("validator")
|
|
85
|
+
else: # pragma: no cover
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
return dc.field(**kwargs)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
field.__doc__ = dc.field.__doc__
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def validate_fields(inst: DataclassProtocol) -> None:
|
|
95
|
+
"""Validate the fields of a dataclass,
|
|
96
|
+
according to `validator` functions set in the field metadata.
|
|
97
|
+
|
|
98
|
+
This function should be called in the `__post_init__` of the dataclass.
|
|
99
|
+
|
|
100
|
+
The validator function should take as input (inst, field, value) and
|
|
101
|
+
raise an exception if the value is invalid.
|
|
102
|
+
:param inst: A dataclasses decorated class instance
|
|
103
|
+
:type inst: DataclassProtocol
|
|
104
|
+
"""
|
|
105
|
+
for field in dc.fields(inst):
|
|
106
|
+
if "validator" not in field.metadata:
|
|
107
|
+
continue
|
|
108
|
+
if isinstance(field.metadata["validator"], list):
|
|
109
|
+
for validator in field.metadata["validator"]:
|
|
110
|
+
validator(inst, field, getattr(inst, field.name))
|
|
111
|
+
else:
|
|
112
|
+
field.metadata["validator"](inst, field, getattr(inst, field.name))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# https://github.com/python/mypy/issues/12155
|
|
116
|
+
UnionAB: TypeAlias = Union[Any, Sequence[Any]]
|
|
117
|
+
ValidatorType: TypeAlias = Callable[[Any, dc.Field[Any], UnionAB], None]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def instance_of(type_: UnionAB) -> ValidatorType:
|
|
121
|
+
"""
|
|
122
|
+
A validator that raises a `TypeError` if the initializer is called
|
|
123
|
+
with a wrong type for this particular attribute (checks are performed using
|
|
124
|
+
`isinstance` therefore it's also valid to pass a tuple of types).
|
|
125
|
+
|
|
126
|
+
:param type_: The type to check for.
|
|
127
|
+
:type type_: typing.Any | collections.abc.Sequence[typing.Any]
|
|
128
|
+
:returns: Validator to check value is instance of type
|
|
129
|
+
:rtype: :py:data:`~sphinx_external_toc_strict._compat.ValidatorType`
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def _validator(inst: Any, attr: dc.Field[Any], value: UnionAB) -> None:
|
|
133
|
+
"""
|
|
134
|
+
We use a callable class to be able to change the ``__repr__``.
|
|
135
|
+
"""
|
|
136
|
+
if not isinstance(value, type_): # type: ignore[arg-type]
|
|
137
|
+
msg_exc = (
|
|
138
|
+
f"'{attr.name}' must be {type_!r} (got {value!r} that "
|
|
139
|
+
f"is a {value.__class__!r})."
|
|
140
|
+
)
|
|
141
|
+
raise TypeError(msg_exc)
|
|
142
|
+
|
|
143
|
+
return cast(
|
|
144
|
+
Callable[[Any, dc.Field[Any], Union[Any, Sequence[Any]]], None], _validator
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def matches_re(regex: str | Pattern[str], flags: int = 0) -> ValidatorType:
|
|
149
|
+
r"""Create a validator fcn for checks against a regex
|
|
150
|
+
|
|
151
|
+
When str and regex are a mismatch raises :py:exc:`ValueError`.
|
|
152
|
+
|
|
153
|
+
:param regex: a regex string or precompiled pattern to match against
|
|
154
|
+
:type regex: str | re.Pattern
|
|
155
|
+
:param flags:
|
|
156
|
+
|
|
157
|
+
Default 0. flags that will be passed to the underlying :py:mod:`re`
|
|
158
|
+
function (default 0)
|
|
159
|
+
|
|
160
|
+
:type flags: int
|
|
161
|
+
:returns: An inline function to validate regex
|
|
162
|
+
:rtype: :py:data:`~sphinx_external_toc_strict._compat.ValidatorType`
|
|
163
|
+
:raises:
|
|
164
|
+
|
|
165
|
+
- :py:exc:`ValueError` -- validator that raises ValueError when
|
|
166
|
+
str arg passed to initializer doesn't match regex
|
|
167
|
+
|
|
168
|
+
- :py:exc:`TypeError` -- flags can only be used with a string
|
|
169
|
+
pattern; pass flags to re.compile() instead
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
fullmatch = getattr(re, "fullmatch", None)
|
|
173
|
+
|
|
174
|
+
if isinstance(regex, Pattern):
|
|
175
|
+
if flags:
|
|
176
|
+
raise TypeError(
|
|
177
|
+
"'flags' can only be used with a string pattern; "
|
|
178
|
+
"pass flags to re.compile() instead"
|
|
179
|
+
)
|
|
180
|
+
pattern = regex
|
|
181
|
+
else:
|
|
182
|
+
pattern = re.compile(regex, flags)
|
|
183
|
+
|
|
184
|
+
if fullmatch:
|
|
185
|
+
match_func = pattern.fullmatch
|
|
186
|
+
else: # Python 2 fullmatch emulation (https://bugs.python.org/issue16203)
|
|
187
|
+
pattern = re.compile(r"(?:{})\Z".format(pattern.pattern), pattern.flags)
|
|
188
|
+
match_func = pattern.match
|
|
189
|
+
|
|
190
|
+
def _validator(inst: Any, attr: dc.Field[Any], value: str) -> None:
|
|
191
|
+
if not match_func(value):
|
|
192
|
+
raise ValueError(
|
|
193
|
+
f"'{attr.name}' must match regex {pattern!r} ({value!r} doesn't)"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return cast(
|
|
197
|
+
Callable[[Any, dc.Field[Any], Union[Any, Sequence[Any]]], None], _validator
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def optional(validator: ValidatorType) -> ValidatorType:
|
|
202
|
+
"""Morphs a validator into an optional (attribute) validator
|
|
203
|
+
|
|
204
|
+
A validator that makes an attribute optional. An optional attribute is one
|
|
205
|
+
which can be set to ``None`` in addition to satisfying the requirements of
|
|
206
|
+
the sub-validator.
|
|
207
|
+
|
|
208
|
+
:param validator: An argument validator
|
|
209
|
+
:type validator: collections.abc.Callable[[typing.Any, dataclasses.Field, typing.Any], None]
|
|
210
|
+
:returns: An inline function optional (attribute) validator
|
|
211
|
+
:rtype: collections.abc.Callable[[typing.Any, dataclasses.Field, typing.Any], None]
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
def _validator(inst: Any, attr: dc.Field[Any], value: UnionAB) -> None:
|
|
215
|
+
if value is None:
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
validator(inst, attr, value)
|
|
219
|
+
|
|
220
|
+
return cast(
|
|
221
|
+
Callable[[Any, dc.Field[Any], Union[Any, Sequence[Any]]], None], _validator
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def deep_iterable(
|
|
226
|
+
member_validator: ValidatorType, iterable_validator: ValidatorType | None = None
|
|
227
|
+
) -> ValidatorType:
|
|
228
|
+
"""A validator that performs deep validation of an iterable.
|
|
229
|
+
|
|
230
|
+
:param member_validator: Validator to apply to iterable members
|
|
231
|
+
:type member_validator: :py:class:`sphinx_external_toc_strict._compat.ValidatorType`
|
|
232
|
+
:param iterable_validator: Default None. Validator to apply to iterable itself
|
|
233
|
+
:type iterable_validator: :py:class:`sphinx_external_toc_strict._compat.ValidatorType` | None
|
|
234
|
+
:returns: Iterator which validates members
|
|
235
|
+
:rtype: :py:class:`sphinx_external_toc_strict._compat.ValidatorType`
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
def _validator(inst: Any, attr: dc.Field[Any], value: UnionAB) -> None:
|
|
239
|
+
if iterable_validator is not None:
|
|
240
|
+
iterable_validator(inst, attr, value)
|
|
241
|
+
|
|
242
|
+
for member in value:
|
|
243
|
+
member_validator(inst, attr, member)
|
|
244
|
+
|
|
245
|
+
return cast(
|
|
246
|
+
Callable[[Any, dc.Field[Any], Union[Any, Sequence[Any]]], None], _validator
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# Docutils compatibility
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def findall(node: Element) -> Any:
|
|
254
|
+
"""findall replaces traverse in docutils v0.18
|
|
255
|
+
|
|
256
|
+
Difference is that findall is an iterator
|
|
257
|
+
|
|
258
|
+
Make findall available for all docutils versions
|
|
259
|
+
|
|
260
|
+
:param node: A parent tree node
|
|
261
|
+
:type node: docutils.nodes.Element
|
|
262
|
+
:returns: Not sure about signature of node.traverse
|
|
263
|
+
:rtype: Iterator[docutils.nodes.Element]
|
|
264
|
+
"""
|
|
265
|
+
return getattr(node, "findall", node.traverse)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '1.1.1'
|
|
16
|
+
__version_tuple__ = version_tuple = (1, 1, 1)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
if sys.version_info >= (3, 8):
|
|
6
|
+
from typing import Final
|
|
7
|
+
else:
|
|
8
|
+
from typing_extensions import Final
|
|
9
|
+
|
|
10
|
+
version: Final[str]
|
|
11
|
+
__version__: Final[str]
|
|
12
|
+
__version_tuple__: Final[tuple[int | str, ...]]
|
|
13
|
+
version_tuple: Final[tuple[int | str, ...]]
|