gp-sphinx 0.0.1a16.dev4__tar.gz → 0.0.1a18.dev0__tar.gz
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.
- {gp_sphinx-0.0.1a16.dev4 → gp_sphinx-0.0.1a18.dev0}/PKG-INFO +7 -7
- {gp_sphinx-0.0.1a16.dev4 → gp_sphinx-0.0.1a18.dev0}/pyproject.toml +7 -7
- {gp_sphinx-0.0.1a16.dev4 → gp_sphinx-0.0.1a18.dev0}/src/gp_sphinx/__init__.py +1 -1
- {gp_sphinx-0.0.1a16.dev4 → gp_sphinx-0.0.1a18.dev0}/src/gp_sphinx/config.py +105 -0
- {gp_sphinx-0.0.1a16.dev4 → gp_sphinx-0.0.1a18.dev0}/src/gp_sphinx/defaults.py +41 -1
- {gp_sphinx-0.0.1a16.dev4 → gp_sphinx-0.0.1a18.dev0}/src/gp_sphinx/myst_lexer.py +122 -2
- {gp_sphinx-0.0.1a16.dev4 → gp_sphinx-0.0.1a18.dev0}/.gitignore +0 -0
- {gp_sphinx-0.0.1a16.dev4 → gp_sphinx-0.0.1a18.dev0}/README.md +0 -0
- {gp_sphinx-0.0.1a16.dev4 → gp_sphinx-0.0.1a18.dev0}/src/gp_sphinx/_compat.py +0 -0
- {gp_sphinx-0.0.1a16.dev4 → gp_sphinx-0.0.1a18.dev0}/src/gp_sphinx/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gp-sphinx
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.1a18.dev0
|
|
4
4
|
Summary: Shared Sphinx documentation platform for git-pull projects
|
|
5
5
|
Project-URL: Bug Tracker, https://github.com/git-pull/gp-sphinx/issues
|
|
6
6
|
Project-URL: Documentation, https://gp-sphinx.git-pull.com
|
|
@@ -27,18 +27,18 @@ Requires-Dist: docutils
|
|
|
27
27
|
Requires-Dist: gp-libs
|
|
28
28
|
Requires-Dist: linkify-it-py
|
|
29
29
|
Requires-Dist: myst-parser
|
|
30
|
-
Requires-Dist: sphinx-autodoc-typehints-gp==0.0.
|
|
30
|
+
Requires-Dist: sphinx-autodoc-typehints-gp==0.0.1a18.dev0
|
|
31
31
|
Requires-Dist: sphinx-copybutton
|
|
32
32
|
Requires-Dist: sphinx-design
|
|
33
|
-
Requires-Dist: sphinx-fonts==0.0.
|
|
34
|
-
Requires-Dist: sphinx-gp-opengraph==0.0.
|
|
35
|
-
Requires-Dist: sphinx-gp-sitemap==0.0.
|
|
36
|
-
Requires-Dist: sphinx-gp-theme==0.0.
|
|
33
|
+
Requires-Dist: sphinx-fonts==0.0.1a18.dev0
|
|
34
|
+
Requires-Dist: sphinx-gp-opengraph==0.0.1a18.dev0
|
|
35
|
+
Requires-Dist: sphinx-gp-sitemap==0.0.1a18.dev0
|
|
36
|
+
Requires-Dist: sphinx-gp-theme==0.0.1a18.dev0
|
|
37
37
|
Requires-Dist: sphinx-inline-tabs
|
|
38
38
|
Requires-Dist: sphinx<9,>=8.1
|
|
39
39
|
Requires-Dist: sphinxext-rediraffe
|
|
40
40
|
Provides-Extra: argparse
|
|
41
|
-
Requires-Dist: sphinx-autodoc-argparse==0.0.
|
|
41
|
+
Requires-Dist: sphinx-autodoc-argparse==0.0.1a18.dev0; extra == 'argparse'
|
|
42
42
|
Description-Content-Type: text/markdown
|
|
43
43
|
|
|
44
44
|
# gp-sphinx · [](https://pypi.org/project/gp-sphinx/) [](https://github.com/git-pull/gp-sphinx/blob/main/LICENSE)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "gp-sphinx"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.1a18.dev0"
|
|
4
4
|
description = "Shared Sphinx documentation platform for git-pull projects"
|
|
5
5
|
requires-python = ">=3.10,<4.0"
|
|
6
6
|
authors = [
|
|
@@ -26,15 +26,15 @@ readme = "README.md"
|
|
|
26
26
|
keywords = ["sphinx", "documentation", "configuration"]
|
|
27
27
|
dependencies = [
|
|
28
28
|
"sphinx>=8.1,<9",
|
|
29
|
-
"sphinx-gp-theme==0.0.
|
|
30
|
-
"sphinx-fonts==0.0.
|
|
29
|
+
"sphinx-gp-theme==0.0.1a18.dev0",
|
|
30
|
+
"sphinx-fonts==0.0.1a18.dev0",
|
|
31
31
|
"myst-parser",
|
|
32
32
|
"docutils",
|
|
33
|
-
"sphinx-autodoc-typehints-gp==0.0.
|
|
33
|
+
"sphinx-autodoc-typehints-gp==0.0.1a18.dev0",
|
|
34
34
|
"sphinx-inline-tabs",
|
|
35
35
|
"sphinx-copybutton",
|
|
36
|
-
"sphinx-gp-opengraph==0.0.
|
|
37
|
-
"sphinx-gp-sitemap==0.0.
|
|
36
|
+
"sphinx-gp-opengraph==0.0.1a18.dev0",
|
|
37
|
+
"sphinx-gp-sitemap==0.0.1a18.dev0",
|
|
38
38
|
"sphinxext-rediraffe",
|
|
39
39
|
"sphinx-design",
|
|
40
40
|
"linkify-it-py",
|
|
@@ -43,7 +43,7 @@ dependencies = [
|
|
|
43
43
|
|
|
44
44
|
[project.optional-dependencies]
|
|
45
45
|
argparse = [
|
|
46
|
-
"sphinx-autodoc-argparse==0.0.
|
|
46
|
+
"sphinx-autodoc-argparse==0.0.1a18.dev0",
|
|
47
47
|
]
|
|
48
48
|
|
|
49
49
|
[project.urls]
|
|
@@ -7,7 +7,7 @@ import logging
|
|
|
7
7
|
__title__ = "gp-sphinx"
|
|
8
8
|
__package_name__ = "gp_sphinx"
|
|
9
9
|
__description__ = "Shared Sphinx documentation platform for git-pull projects"
|
|
10
|
-
__version__ = "0.0.
|
|
10
|
+
__version__ = "0.0.1a18.dev0"
|
|
11
11
|
__author__ = "Tony Narlock"
|
|
12
12
|
__github__ = "https://github.com/git-pull/gp-sphinx"
|
|
13
13
|
__docs__ = "https://gp-sphinx.git-pull.com"
|
|
@@ -32,6 +32,7 @@ import json
|
|
|
32
32
|
import logging
|
|
33
33
|
import os.path
|
|
34
34
|
import pathlib
|
|
35
|
+
import sys
|
|
35
36
|
import typing as t
|
|
36
37
|
|
|
37
38
|
from gp_sphinx.defaults import (
|
|
@@ -39,6 +40,7 @@ from gp_sphinx.defaults import (
|
|
|
39
40
|
DEFAULT_AUTODOC_CLASS_SIGNATURE,
|
|
40
41
|
DEFAULT_AUTODOC_MEMBER_ORDER,
|
|
41
42
|
DEFAULT_AUTODOC_OPTIONS,
|
|
43
|
+
DEFAULT_AUTODOC_PRESERVE_DEFAULTS,
|
|
42
44
|
DEFAULT_AUTODOC_TYPEHINTS,
|
|
43
45
|
DEFAULT_COPYBUTTON_LINE_CONTINUATION_CHARACTER,
|
|
44
46
|
DEFAULT_COPYBUTTON_PROMPT_IS_REGEXP,
|
|
@@ -206,6 +208,108 @@ def make_linkcode_resolve(
|
|
|
206
208
|
return linkcode_resolve
|
|
207
209
|
|
|
208
210
|
|
|
211
|
+
def make_workspace_linkcode_resolve(
|
|
212
|
+
*,
|
|
213
|
+
repo_root: pathlib.Path | str,
|
|
214
|
+
github_url: str,
|
|
215
|
+
source_branch: str = "main",
|
|
216
|
+
) -> Callable[[str, dict[str, str]], str | None]:
|
|
217
|
+
"""Create a ``linkcode_resolve`` function for a uv/pnpm workspace monorepo.
|
|
218
|
+
|
|
219
|
+
Unlike :func:`make_linkcode_resolve`, which assumes a single-package layout
|
|
220
|
+
rooted at ``<repo>/<src_dir>/<package>/…``, this resolver computes URLs by
|
|
221
|
+
taking the absolute path returned by :func:`inspect.getsourcefile` and
|
|
222
|
+
making it relative to *repo_root*. This works uniformly across all packages
|
|
223
|
+
in a workspace layout such as ``<repo>/packages/<pkg>/src/<module>/…``
|
|
224
|
+
without requiring per-package registration.
|
|
225
|
+
|
|
226
|
+
The URL always points to *source_branch* — there is no per-package version
|
|
227
|
+
tag branching because each workspace package carries its own independent
|
|
228
|
+
version string while the docs site tracks the live monorepo tip.
|
|
229
|
+
|
|
230
|
+
Returns ``None`` when the domain is not ``"py"``, the module is not
|
|
231
|
+
imported, the attribute cannot be resolved, ``inspect.getsourcefile``
|
|
232
|
+
returns a falsy value, or the source file lives outside *repo_root*.
|
|
233
|
+
|
|
234
|
+
Parameters
|
|
235
|
+
----------
|
|
236
|
+
repo_root : pathlib.Path or str
|
|
237
|
+
Absolute path to the repository root (the directory that contains
|
|
238
|
+
``pyproject.toml`` and the ``packages/`` tree).
|
|
239
|
+
github_url : str
|
|
240
|
+
Base GitHub repository URL, e.g.
|
|
241
|
+
``"https://github.com/git-pull/gp-sphinx"``.
|
|
242
|
+
source_branch : str
|
|
243
|
+
Branch used in all generated URLs (default ``"main"``).
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
Callable[[str, dict[str, str]], str | None]
|
|
248
|
+
A function suitable for ``linkcode_resolve`` in a Sphinx config.
|
|
249
|
+
|
|
250
|
+
Examples
|
|
251
|
+
--------
|
|
252
|
+
>>> import pathlib
|
|
253
|
+
>>> resolver = make_workspace_linkcode_resolve(
|
|
254
|
+
... repo_root=pathlib.Path("/tmp/repo"),
|
|
255
|
+
... github_url="https://github.com/git-pull/gp-sphinx",
|
|
256
|
+
... )
|
|
257
|
+
>>> callable(resolver)
|
|
258
|
+
True
|
|
259
|
+
>>> resolver("c", {"module": "x", "fullname": "y"}) is None
|
|
260
|
+
True
|
|
261
|
+
"""
|
|
262
|
+
root = pathlib.Path(repo_root).resolve()
|
|
263
|
+
|
|
264
|
+
def linkcode_resolve(domain: str, info: dict[str, str]) -> str | None:
|
|
265
|
+
if domain != "py":
|
|
266
|
+
return None
|
|
267
|
+
|
|
268
|
+
modname = info["module"]
|
|
269
|
+
fullname = info["fullname"]
|
|
270
|
+
|
|
271
|
+
submod = sys.modules.get(modname)
|
|
272
|
+
if submod is None:
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
obj: object = submod
|
|
276
|
+
for part in fullname.split("."):
|
|
277
|
+
try:
|
|
278
|
+
obj = getattr(obj, part)
|
|
279
|
+
except Exception: # noqa: PERF203
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
unwrap = inspect.unwrap
|
|
284
|
+
except AttributeError:
|
|
285
|
+
pass
|
|
286
|
+
else:
|
|
287
|
+
if callable(obj):
|
|
288
|
+
obj = unwrap(obj)
|
|
289
|
+
|
|
290
|
+
try:
|
|
291
|
+
fn = inspect.getsourcefile(obj) # type: ignore[arg-type]
|
|
292
|
+
except Exception:
|
|
293
|
+
fn = None
|
|
294
|
+
if not fn:
|
|
295
|
+
return None
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
source, lineno = inspect.getsourcelines(obj) # type: ignore[arg-type]
|
|
299
|
+
except Exception:
|
|
300
|
+
lineno = None
|
|
301
|
+
|
|
302
|
+
linespec = f"#L{lineno}-L{lineno + len(source) - 1}" if lineno else ""
|
|
303
|
+
|
|
304
|
+
rel = os.path.relpath(fn, start=root)
|
|
305
|
+
if rel.startswith(".."):
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
return f"{github_url}/blob/{source_branch}/{rel}{linespec}"
|
|
309
|
+
|
|
310
|
+
return linkcode_resolve
|
|
311
|
+
|
|
312
|
+
|
|
209
313
|
def merge_sphinx_config(
|
|
210
314
|
*,
|
|
211
315
|
project: str,
|
|
@@ -442,6 +546,7 @@ def merge_sphinx_config(
|
|
|
442
546
|
"autodoc_member_order": DEFAULT_AUTODOC_MEMBER_ORDER,
|
|
443
547
|
"autodoc_class_signature": DEFAULT_AUTODOC_CLASS_SIGNATURE,
|
|
444
548
|
"autodoc_typehints": DEFAULT_AUTODOC_TYPEHINTS,
|
|
549
|
+
"autodoc_preserve_defaults": DEFAULT_AUTODOC_PRESERVE_DEFAULTS,
|
|
445
550
|
"toc_object_entries_show_parents": DEFAULT_TOC_OBJECT_ENTRIES_SHOW_PARENTS,
|
|
446
551
|
"autodoc_default_options": dict(DEFAULT_AUTODOC_OPTIONS),
|
|
447
552
|
# Copybutton
|
|
@@ -232,15 +232,32 @@ Examples
|
|
|
232
232
|
|
|
233
233
|
DEFAULT_SPHINX_FONT_PRELOAD: list[tuple[str, int, str]] = [
|
|
234
234
|
("IBM Plex Sans", 400, "normal"),
|
|
235
|
+
("IBM Plex Sans", 500, "normal"),
|
|
236
|
+
("IBM Plex Sans", 600, "normal"),
|
|
235
237
|
("IBM Plex Sans", 700, "normal"),
|
|
238
|
+
("IBM Plex Sans", 400, "italic"),
|
|
236
239
|
("IBM Plex Mono", 400, "normal"),
|
|
240
|
+
("IBM Plex Mono", 700, "normal"),
|
|
237
241
|
]
|
|
238
242
|
"""Font preload hints for critical rendering path.
|
|
239
243
|
|
|
244
|
+
Each entry is ``(family, weight, style)``. Faces in this list are
|
|
245
|
+
emitted as ``<link rel="preload" as="font">`` tags so the browser
|
|
246
|
+
fetches them in parallel with the critical CSS/HTML, before
|
|
247
|
+
``font-display: block`` would otherwise hide text waiting on a
|
|
248
|
+
lazy ``@font-face`` request.
|
|
249
|
+
|
|
250
|
+
The list covers every face Furo / ``furo-tw.css`` is observed to
|
|
251
|
+
demand above the fold: body (Sans 400), headings + sidebar labels
|
|
252
|
+
(Sans 500), epigraph blockquotes (Sans 600), strong / current
|
|
253
|
+
sidebar (Sans 700), inline ``<em>`` and announcement-bar emphasis
|
|
254
|
+
(Sans 400 italic), code blocks (Mono 400), and bold inline code
|
|
255
|
+
``<strong><code>`` (Mono 700).
|
|
256
|
+
|
|
240
257
|
Examples
|
|
241
258
|
--------
|
|
242
259
|
>>> len(DEFAULT_SPHINX_FONT_PRELOAD)
|
|
243
|
-
|
|
260
|
+
7
|
|
244
261
|
"""
|
|
245
262
|
|
|
246
263
|
DEFAULT_SPHINX_FONT_FALLBACKS: list[dict[str, str]] = [
|
|
@@ -346,6 +363,29 @@ Examples
|
|
|
346
363
|
'description'
|
|
347
364
|
"""
|
|
348
365
|
|
|
366
|
+
DEFAULT_AUTODOC_PRESERVE_DEFAULTS: bool = True
|
|
367
|
+
"""Preserve source text of parameter defaults instead of ``repr()``.
|
|
368
|
+
|
|
369
|
+
When ``True``, Sphinx's ``update_default_value`` listener wraps each
|
|
370
|
+
``inspect.Parameter.default`` in a ``DefaultValue`` shim whose
|
|
371
|
+
``__repr__`` returns the *literal source text* of the default
|
|
372
|
+
expression. The result is that signatures render
|
|
373
|
+
``scope=DEFAULT_OPTION_SCOPE`` instead of
|
|
374
|
+
``scope=<libtmux.constants._DefaultOptionScope object>`` and
|
|
375
|
+
``retry_exceptions=(libtmux_exc.LibTmuxException,)`` instead of
|
|
376
|
+
``(<class 'libtmux.exc.LibTmuxException'>,)``.
|
|
377
|
+
|
|
378
|
+
The flag has no effect on synthetic ``__init__`` (dataclass / attrs /
|
|
379
|
+
NamedTuple) where ``inspect.getsource()`` returns nothing — Sphinx's
|
|
380
|
+
listener bails out for those, leaving the defaults as ``=<factory>``
|
|
381
|
+
until a sibling listener handles them.
|
|
382
|
+
|
|
383
|
+
Examples
|
|
384
|
+
--------
|
|
385
|
+
>>> DEFAULT_AUTODOC_PRESERVE_DEFAULTS
|
|
386
|
+
True
|
|
387
|
+
"""
|
|
388
|
+
|
|
349
389
|
DEFAULT_COPYBUTTON_LINE_CONTINUATION_CHARACTER: str = "\\"
|
|
350
390
|
"""Line continuation character for sphinx-copybutton."""
|
|
351
391
|
|
|
@@ -30,7 +30,7 @@ from __future__ import annotations
|
|
|
30
30
|
import typing as t
|
|
31
31
|
|
|
32
32
|
from pygments.lexers.markup import MarkdownLexer, RstLexer
|
|
33
|
-
from pygments.token import String, Whitespace
|
|
33
|
+
from pygments.token import Name, String, Text, Whitespace
|
|
34
34
|
|
|
35
35
|
if t.TYPE_CHECKING:
|
|
36
36
|
import re
|
|
@@ -60,7 +60,15 @@ class MystLexer(MarkdownLexer):
|
|
|
60
60
|
>>> tokens = [(str(tok), v) for tok, v in lexer.get_tokens("Hello")]
|
|
61
61
|
>>> any(v == "Hello" for _, v in tokens)
|
|
62
62
|
True
|
|
63
|
-
|
|
63
|
+
|
|
64
|
+
Triple-colon fences (MyST ``colon_fence`` extension) tokenize the
|
|
65
|
+
opening, option keys, and closing markers so source samples
|
|
66
|
+
documenting MyST directives render with proper highlighting:
|
|
67
|
+
|
|
68
|
+
>>> tokens = tokenize_myst(":::{note}\\nhi\\n:::\\n")
|
|
69
|
+
>>> any(":::{note}" == v for _, v in tokens)
|
|
70
|
+
True
|
|
71
|
+
""" # noqa: D301 - backslashes are in doctest code, not escape sequences
|
|
64
72
|
|
|
65
73
|
name = "MyST Markdown"
|
|
66
74
|
aliases: t.ClassVar[list[str]] = ["myst", "myst-md"]
|
|
@@ -124,6 +132,97 @@ class MystLexer(MarkdownLexer):
|
|
|
124
132
|
|
|
125
133
|
yield match.start("closing"), String.Backtick, match.group("closing")
|
|
126
134
|
|
|
135
|
+
def _handle_colon_fence(
|
|
136
|
+
self,
|
|
137
|
+
match: re.Match[str],
|
|
138
|
+
) -> t.Iterator[tuple[int, _TokenType, str]]:
|
|
139
|
+
"""Lex a ``:::{<directive>}`` colon-fenced block (MyST ``colon_fence``).
|
|
140
|
+
|
|
141
|
+
Emits the opening ``:::{name}`` line and the closing ``:::`` line
|
|
142
|
+
as ``String.Backtick`` (matching how :meth:`_handle_eval_rst`
|
|
143
|
+
renders fence boundaries). Within the body, lines matching the
|
|
144
|
+
``:<key>: <value>`` MyST option syntax tokenize the key as
|
|
145
|
+
``Name.Tag`` (RST-style option-list convention) and the value as
|
|
146
|
+
``Text``; remaining body content is ``Text``.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
match : re.Match[str]
|
|
151
|
+
Regex match with named groups ``opening``, ``newline``,
|
|
152
|
+
``body``, and ``closing``.
|
|
153
|
+
|
|
154
|
+
Yields
|
|
155
|
+
------
|
|
156
|
+
tuple[int, _TokenType, str]
|
|
157
|
+
``(offset, token_type, value)`` triples whose offsets are
|
|
158
|
+
relative to the start of the full document, suitable for
|
|
159
|
+
``get_tokens_unprocessed``.
|
|
160
|
+
|
|
161
|
+
Notes
|
|
162
|
+
-----
|
|
163
|
+
Handles exactly three colons (``:::``). Four-or-more-colon
|
|
164
|
+
variants (``::::{name}``) are a known MyST feature but are not
|
|
165
|
+
currently in scope; if they appear later, capture the opening
|
|
166
|
+
colon count and require the closing count to match via a
|
|
167
|
+
backreference.
|
|
168
|
+
|
|
169
|
+
Body content is emitted as plain ``Text`` (plus ``Name.Tag`` for
|
|
170
|
+
option keys). MyST directive bodies vary by directive — e.g.
|
|
171
|
+
``:::{grid}`` carries sphinx-design markup, ``:::{tab-set}``
|
|
172
|
+
carries inline content — so a single inner-language delegation
|
|
173
|
+
is not appropriate. Specialized inner highlighting can be added
|
|
174
|
+
per directive in a follow-up.
|
|
175
|
+
|
|
176
|
+
Examples
|
|
177
|
+
--------
|
|
178
|
+
>>> tokens = tokenize_myst(
|
|
179
|
+
... ":::{auto-pytest-plugin} my_project.pp\\n"
|
|
180
|
+
... ":package: my-project\\n"
|
|
181
|
+
... ":::\\n"
|
|
182
|
+
... )
|
|
183
|
+
>>> any(":::{auto-pytest-plugin}" in v for _, v in tokens)
|
|
184
|
+
True
|
|
185
|
+
>>> any("Name.Tag" in tok and v == ":package:" for tok, v in tokens)
|
|
186
|
+
True
|
|
187
|
+
""" # noqa: D301 - backslashes are in doctest code, not escape sequences
|
|
188
|
+
import re as _re
|
|
189
|
+
|
|
190
|
+
yield match.start("opening"), String.Backtick, match.group("opening")
|
|
191
|
+
yield match.start("newline"), Whitespace, match.group("newline")
|
|
192
|
+
|
|
193
|
+
body = match.group("body")
|
|
194
|
+
body_offset = match.start("body")
|
|
195
|
+
# Walk the body line-by-line. Lines matching ``:<key>:[ <value>]``
|
|
196
|
+
# tokenize the key as Name.Tag; everything else is Text.
|
|
197
|
+
# Pattern: leading ``:``, identifier, trailing ``:``, then
|
|
198
|
+
# optional whitespace + value through end of line.
|
|
199
|
+
option_re = _re.compile(r"^(:[\w\-]+:)([^\n]*)(\n)", _re.MULTILINE)
|
|
200
|
+
cursor = 0
|
|
201
|
+
for option_match in option_re.finditer(body):
|
|
202
|
+
# Emit any text between the previous match end and this
|
|
203
|
+
# match's start as plain Text.
|
|
204
|
+
if option_match.start() > cursor:
|
|
205
|
+
preceding = body[cursor : option_match.start()]
|
|
206
|
+
yield body_offset + cursor, Text, preceding
|
|
207
|
+
yield (
|
|
208
|
+
body_offset + option_match.start(1),
|
|
209
|
+
Name.Tag,
|
|
210
|
+
option_match.group(1),
|
|
211
|
+
)
|
|
212
|
+
value = option_match.group(2)
|
|
213
|
+
if value:
|
|
214
|
+
yield body_offset + option_match.start(2), Text, value
|
|
215
|
+
yield (
|
|
216
|
+
body_offset + option_match.start(3),
|
|
217
|
+
Whitespace,
|
|
218
|
+
option_match.group(3),
|
|
219
|
+
)
|
|
220
|
+
cursor = option_match.end()
|
|
221
|
+
if cursor < len(body):
|
|
222
|
+
yield body_offset + cursor, Text, body[cursor:]
|
|
223
|
+
|
|
224
|
+
yield match.start("closing"), String.Backtick, match.group("closing")
|
|
225
|
+
|
|
127
226
|
# tokens must be declared AFTER _handle_eval_rst because the class
|
|
128
227
|
# body is executed sequentially and the dict literal references
|
|
129
228
|
# _handle_eval_rst by name.
|
|
@@ -150,6 +249,27 @@ class MystLexer(MarkdownLexer):
|
|
|
150
249
|
),
|
|
151
250
|
_handle_eval_rst,
|
|
152
251
|
),
|
|
252
|
+
# Triple-colon fence (MyST colon_fence extension):
|
|
253
|
+
# :::{<directive>}[ <info-string>] ... :::
|
|
254
|
+
#
|
|
255
|
+
# The MarkdownLexer parent has no rule for these so they
|
|
256
|
+
# fall through to plain Token.Text without this handler.
|
|
257
|
+
# Handles exactly three colons; four-or-more variants are
|
|
258
|
+
# out of scope (see _handle_colon_fence Notes).
|
|
259
|
+
(
|
|
260
|
+
(
|
|
261
|
+
# group opening: ::: fence + braced directive name +
|
|
262
|
+
# optional info string (e.g. :::{tab-set} centered)
|
|
263
|
+
r"(?P<opening>^:::\{[\w\-]+\}[^\n]*)"
|
|
264
|
+
r"(?P<newline>\n)"
|
|
265
|
+
# group body: directive content, non-greedy to stop
|
|
266
|
+
# at the first closing fence
|
|
267
|
+
r"(?P<body>(?:.|\n)*?)"
|
|
268
|
+
# group closing: bare ::: at start of line
|
|
269
|
+
r"(?P<closing>^:::[ \t]*$\n?)"
|
|
270
|
+
),
|
|
271
|
+
_handle_colon_fence,
|
|
272
|
+
),
|
|
153
273
|
# All MarkdownLexer root rules follow unchanged, providing
|
|
154
274
|
# highlighting for normal fenced code blocks, inline code,
|
|
155
275
|
# headings, etc.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|