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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gp-sphinx
3
- Version: 0.0.1a16.dev4
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.1a16.dev4
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.1a16.dev4
34
- Requires-Dist: sphinx-gp-opengraph==0.0.1a16.dev4
35
- Requires-Dist: sphinx-gp-sitemap==0.0.1a16.dev4
36
- Requires-Dist: sphinx-gp-theme==0.0.1a16.dev4
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.1a16.dev4; extra == 'argparse'
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 &middot; [![Python Package](https://img.shields.io/pypi/v/gp-sphinx.svg)](https://pypi.org/project/gp-sphinx/) [![License](https://img.shields.io/github/license/git-pull/gp-sphinx.svg)](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.1a16.dev4"
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.1a16.dev4",
30
- "sphinx-fonts==0.0.1a16.dev4",
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.1a16.dev4",
33
+ "sphinx-autodoc-typehints-gp==0.0.1a18.dev0",
34
34
  "sphinx-inline-tabs",
35
35
  "sphinx-copybutton",
36
- "sphinx-gp-opengraph==0.0.1a16.dev4",
37
- "sphinx-gp-sitemap==0.0.1a16.dev4",
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.1a16.dev4",
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.1a16.dev4"
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
- 3
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.