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.
Files changed (35) hide show
  1. sphinx_external_toc_strict/__init__.py +47 -0
  2. sphinx_external_toc_strict/__init__.pyi +12 -0
  3. sphinx_external_toc_strict/_compat.py +265 -0
  4. sphinx_external_toc_strict/_version.py +16 -0
  5. sphinx_external_toc_strict/_version.pyi +13 -0
  6. sphinx_external_toc_strict/api.py +424 -0
  7. sphinx_external_toc_strict/api.pyi +105 -0
  8. sphinx_external_toc_strict/cli.py +251 -0
  9. sphinx_external_toc_strict/cli.pyi +23 -0
  10. sphinx_external_toc_strict/constants.py +122 -0
  11. sphinx_external_toc_strict/constants.pyi +31 -0
  12. sphinx_external_toc_strict/events.py +477 -0
  13. sphinx_external_toc_strict/events.pyi +47 -0
  14. sphinx_external_toc_strict/exceptions.py +18 -0
  15. sphinx_external_toc_strict/exceptions.pyi +10 -0
  16. sphinx_external_toc_strict/filename_suffix.py +194 -0
  17. sphinx_external_toc_strict/filename_suffix.pyi +20 -0
  18. sphinx_external_toc_strict/parsing_shared.py +340 -0
  19. sphinx_external_toc_strict/parsing_shared.pyi +68 -0
  20. sphinx_external_toc_strict/parsing_strictyaml.py +593 -0
  21. sphinx_external_toc_strict/parsing_strictyaml.pyi +65 -0
  22. sphinx_external_toc_strict/pep518_read.py +241 -0
  23. sphinx_external_toc_strict/pep518_read.pyi +29 -0
  24. sphinx_external_toc_strict/py.typed +1 -0
  25. sphinx_external_toc_strict/tools_strictyaml.py +578 -0
  26. sphinx_external_toc_strict/tools_strictyaml.pyi +60 -0
  27. sphinx_external_toc_strict/version_semantic.py +331 -0
  28. sphinx_external_toc_strict/version_semantic.pyi +18 -0
  29. sphinx_external_toc_strict-1.1.1.dist-info/LICENSE +21 -0
  30. sphinx_external_toc_strict-1.1.1.dist-info/METADATA +820 -0
  31. sphinx_external_toc_strict-1.1.1.dist-info/NOTICE.txt +14 -0
  32. sphinx_external_toc_strict-1.1.1.dist-info/RECORD +35 -0
  33. sphinx_external_toc_strict-1.1.1.dist-info/WHEEL +5 -0
  34. sphinx_external_toc_strict-1.1.1.dist-info/entry_points.txt +5 -0
  35. 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, ...]]