|
1 | | -# flake8: NOQA: E501 |
2 | 1 | """Sphinx configuration for libtmux.""" |
3 | 2 |
|
4 | 3 | from __future__ import annotations |
5 | 4 |
|
6 | | -import contextlib |
7 | | -import inspect |
8 | 5 | import pathlib |
9 | 6 | import sys |
10 | | -import typing as t |
11 | | -from os.path import relpath |
12 | 7 |
|
13 | 8 | import libtmux |
14 | 9 |
|
15 | | -if t.TYPE_CHECKING: |
16 | | - from sphinx.application import Sphinx |
| 10 | +from gp_sphinx.config import make_linkcode_resolve, merge_sphinx_config |
17 | 11 |
|
18 | 12 | # Get the project root dir, which is the parent dir of this |
19 | 13 | cwd = pathlib.Path(__file__).parent |
20 | 14 | project_root = cwd.parent |
21 | 15 | project_src = project_root / "src" |
22 | 16 |
|
23 | 17 | sys.path.insert(0, str(project_src)) |
24 | | -sys.path.insert(0, str(cwd / "_ext")) |
25 | 18 |
|
26 | 19 | # package data |
27 | 20 | about: dict[str, str] = {} |
28 | 21 | with (project_src / "libtmux" / "__about__.py").open() as fp: |
29 | 22 | exec(fp.read(), about) |
30 | 23 |
|
31 | | -extensions = [ |
32 | | - "sphinx.ext.autodoc", |
33 | | - "sphinx_pytest_fixtures", |
34 | | - "sphinx_fonts", |
35 | | - "sphinx.ext.intersphinx", |
36 | | - "sphinx_autodoc_typehints", |
37 | | - "sphinx.ext.todo", |
38 | | - "sphinx.ext.linkcode", |
39 | | - "sphinx.ext.napoleon", |
40 | | - "sphinx_inline_tabs", |
41 | | - "sphinx_copybutton", |
42 | | - "sphinxext.opengraph", |
43 | | - "sphinxext.rediraffe", |
44 | | - "myst_parser", |
45 | | - "linkify_issues", |
46 | | - "sphinx_design", |
47 | | -] |
48 | | - |
49 | | -myst_enable_extensions = [ |
50 | | - "colon_fence", |
51 | | - "substitution", |
52 | | - "replacements", |
53 | | - "strikethrough", |
54 | | - "linkify", |
55 | | -] |
56 | | - |
57 | | -myst_heading_anchors = 4 |
58 | | - |
59 | | -templates_path = ["_templates"] |
60 | | - |
61 | | -source_suffix = {".rst": "restructuredtext", ".md": "markdown"} |
62 | | - |
63 | | -master_doc = "index" |
64 | | - |
65 | | -project = about["__title__"] |
66 | | -project_copyright = about["__copyright__"] |
67 | | - |
68 | | -version = "{}".format(".".join(about["__version__"].split("."))[:2]) |
69 | | -release = "{}".format(about["__version__"]) |
70 | | - |
71 | | -exclude_patterns = ["_build"] |
72 | | - |
73 | | -pygments_style = "monokai" |
74 | | -pygments_dark_style = "monokai" |
75 | | - |
76 | | -html_favicon = "_static/favicon.ico" |
77 | | -html_static_path = ["_static"] |
78 | | -html_css_files = ["css/custom.css"] |
79 | | -html_extra_path = ["manifest.json"] |
80 | | -html_theme = "furo" |
81 | | -html_theme_path: list[str] = [] |
82 | | -html_theme_options: dict[str, str | list[dict[str, str]]] = { |
83 | | - "light_logo": "img/libtmux.svg", |
84 | | - "dark_logo": "img/libtmux.svg", |
85 | | - "footer_icons": [ |
86 | | - { |
87 | | - "name": "GitHub", |
88 | | - "url": about["__github__"], |
89 | | - "html": """ |
90 | | - <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16"> |
91 | | - <path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path> |
92 | | - </svg> |
93 | | - """, |
94 | | - "class": "", |
95 | | - }, |
96 | | - ], |
97 | | - "source_repository": f"{about['__github__']}/", |
98 | | - "source_branch": "master", |
99 | | - "source_directory": "docs/", |
100 | | - "announcement": "<em>Friendly reminder:</em> 📌 Pin the package, libtmux is pre-1.0 and APIs will be <a href='/migration.html'>changing</a> throughout 2026.", |
101 | | -} |
102 | | -html_sidebars = { |
103 | | - "**": [ |
104 | | - "sidebar/scroll-start.html", |
105 | | - "sidebar/brand.html", |
106 | | - "sidebar/search.html", |
107 | | - "sidebar/navigation.html", |
108 | | - "sidebar/projects.html", |
109 | | - "sidebar/scroll-end.html", |
110 | | - ], |
111 | | -} |
112 | | - |
113 | | -# linkify_issues |
114 | | -issue_url_tpl = f"{about['__github__']}/issues/{{issue_id}}" |
115 | | - |
116 | | -# sphinx.ext.autodoc |
117 | | -autoclass_content = "both" |
118 | | -autodoc_member_order = "bysource" |
119 | | -# Automatically extract typehints when specified and place them in |
120 | | -# descriptions of the relevant function/method. |
121 | | -autodoc_typehints = "description" |
122 | | -# Don't show class signature with the class' name. |
123 | | -autodoc_class_signature = "separated" |
124 | | -toc_object_entries_show_parents = "hide" |
125 | | - |
126 | | -# sphinx-autodoc-typehints |
127 | | -# Suppress warnings for forward references that can't be resolved |
128 | | -# (types in TYPE_CHECKING blocks used for circular import avoidance) |
129 | | -suppress_warnings = [ |
130 | | - "sphinx_autodoc_typehints.forward_reference", |
131 | | -] |
132 | | - |
133 | | -# sphinx-copybutton |
134 | | -copybutton_prompt_text = ( |
135 | | - r">>> |\.\.\. |> |\$ |\# | In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " |
136 | | -) |
137 | | -copybutton_prompt_is_regexp = True |
138 | | -copybutton_remove_prompts = True |
139 | | -copybutton_line_continuation_character = "\\" |
140 | | - |
141 | | -# sphinxext-rediraffe |
142 | | -rediraffe_redirects = "redirects.txt" |
143 | | -rediraffe_branch = "master~1" |
144 | | - |
145 | | -# sphinxext.opengraph |
146 | | -ogp_site_url = about["__docs__"] |
147 | | -ogp_image = "_static/img/icons/icon-192x192.png" |
148 | | -ogp_site_name = about["__title__"] |
149 | | - |
150 | | -# sphinx_fonts — self-hosted IBM Plex via Fontsource CDN |
151 | | -sphinx_fonts = [ |
152 | | - { |
153 | | - "family": "IBM Plex Sans", |
154 | | - "package": "@fontsource/ibm-plex-sans", |
155 | | - "version": "5.2.8", |
156 | | - "weights": [400, 500, 600, 700], |
157 | | - "styles": ["normal", "italic"], |
158 | | - "subsets": ["latin", "latin-ext"], |
159 | | - }, |
160 | | - { |
161 | | - "family": "IBM Plex Mono", |
162 | | - "package": "@fontsource/ibm-plex-mono", |
163 | | - "version": "5.2.7", |
164 | | - "weights": [400, 500, 600, 700], |
165 | | - "styles": ["normal", "italic"], |
166 | | - "subsets": ["latin", "latin-ext"], |
| 24 | +conf = merge_sphinx_config( |
| 25 | + project=about["__title__"], |
| 26 | + version=about["__version__"], |
| 27 | + copyright=about["__copyright__"], |
| 28 | + source_repository=f"{about['__github__']}/", |
| 29 | + docs_url=about["__docs__"], |
| 30 | + source_branch="master", |
| 31 | + light_logo="img/libtmux.svg", |
| 32 | + dark_logo="img/libtmux.svg", |
| 33 | + extra_extensions=["sphinx_pytest_fixtures", "sphinx.ext.todo"], |
| 34 | + intersphinx_mapping={ |
| 35 | + "python": ("https://docs.python.org/", None), |
| 36 | + "pytest": ("https://docs.pytest.org/en/stable/", None), |
167 | 37 | }, |
168 | | -] |
169 | | - |
170 | | -sphinx_font_preload = [ |
171 | | - ("IBM Plex Sans", 400, "normal"), # body text |
172 | | - ("IBM Plex Sans", 700, "normal"), # headings |
173 | | - ("IBM Plex Mono", 400, "normal"), # code blocks |
174 | | -] |
175 | | - |
176 | | -sphinx_font_fallbacks = [ |
177 | | - { |
178 | | - "family": "IBM Plex Sans Fallback", |
179 | | - "src": 'local("Arial"), local("Helvetica Neue"), local("Helvetica")', |
180 | | - "size_adjust": "110.6%", |
181 | | - "ascent_override": "92.7%", |
182 | | - "descent_override": "24.9%", |
183 | | - "line_gap_override": "0%", |
| 38 | + linkcode_resolve=make_linkcode_resolve( |
| 39 | + libtmux, about["__github__"], src_dir="src" |
| 40 | + ), |
| 41 | + # Project-specific overrides |
| 42 | + theme_options={ |
| 43 | + "announcement": ( |
| 44 | + "<em>Friendly reminder:</em> 📌 Pin the package, libtmux is" |
| 45 | + " pre-1.0 and APIs will be <a href='/migration.html'>changing</a>" |
| 46 | + " throughout 2026." |
| 47 | + ), |
184 | 48 | }, |
185 | | - { |
186 | | - "family": "IBM Plex Mono Fallback", |
187 | | - "src": 'local("Courier New"), local("Courier")', |
188 | | - "size_adjust": "100%", |
189 | | - "ascent_override": "102.5%", |
190 | | - "descent_override": "27.5%", |
191 | | - "line_gap_override": "0%", |
192 | | - }, |
193 | | -] |
194 | | - |
195 | | -sphinx_font_css_variables = { |
196 | | - "--font-stack": '"IBM Plex Sans", "IBM Plex Sans Fallback", -apple-system, BlinkMacSystemFont, sans-serif', |
197 | | - "--font-stack--monospace": '"IBM Plex Mono", "IBM Plex Mono Fallback", SFMono-Regular, Menlo, Consolas, monospace', |
198 | | - "--font-stack--headings": "var(--font-stack)", |
199 | | -} |
200 | | - |
201 | | -intersphinx_mapping = { |
202 | | - "python": ("https://docs.python.org/", None), |
203 | | - "pytest": ("https://docs.pytest.org/en/stable/", None), |
204 | | -} |
205 | | - |
206 | | - |
207 | | -def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str: |
208 | | - """ |
209 | | - Determine the URL corresponding to Python object. |
210 | | -
|
211 | | - Notes |
212 | | - ----- |
213 | | - From https://github.com/numpy/numpy/blob/v1.15.1/doc/source/conf.py, 7c49cfa |
214 | | - on Jul 31. License BSD-3. https://github.com/numpy/numpy/blob/v1.15.1/LICENSE.txt |
215 | | - """ |
216 | | - if domain != "py": |
217 | | - return None |
218 | | - |
219 | | - modname = info["module"] |
220 | | - fullname = info["fullname"] |
221 | | - |
222 | | - submod = sys.modules.get(modname) |
223 | | - if submod is None: |
224 | | - return None |
225 | | - |
226 | | - obj = submod |
227 | | - for part in fullname.split("."): |
228 | | - try: |
229 | | - obj = getattr(obj, part) |
230 | | - except Exception: # noqa: PERF203 |
231 | | - return None |
232 | | - |
233 | | - # strip decorators, which would resolve to the source of the decorator |
234 | | - # possibly an upstream bug in getsourcefile, bpo-1764286 |
235 | | - try: |
236 | | - unwrap = inspect.unwrap |
237 | | - except AttributeError: |
238 | | - pass |
239 | | - else: |
240 | | - if callable(obj): |
241 | | - obj = unwrap(obj) |
242 | | - |
243 | | - try: |
244 | | - fn = inspect.getsourcefile(obj) |
245 | | - except Exception: |
246 | | - fn = None |
247 | | - if not fn: |
248 | | - return None |
249 | | - |
250 | | - try: |
251 | | - source, lineno = inspect.getsourcelines(obj) |
252 | | - except Exception: |
253 | | - lineno = None |
254 | | - |
255 | | - linespec = f"#L{lineno}-L{lineno + len(source) - 1}" if lineno else "" |
256 | | - |
257 | | - fn = relpath(fn, start=pathlib.Path(libtmux.__file__).parent) |
258 | | - |
259 | | - if "dev" in about["__version__"]: |
260 | | - return "{}/blob/master/{}/{}/{}{}".format( |
261 | | - about["__github__"], |
262 | | - "src", |
263 | | - about["__package_name__"], |
264 | | - fn, |
265 | | - linespec, |
266 | | - ) |
267 | | - return "{}/blob/v{}/{}/{}/{}{}".format( |
268 | | - about["__github__"], |
269 | | - about["__version__"], |
270 | | - "src", |
271 | | - about["__package_name__"], |
272 | | - fn, |
273 | | - linespec, |
274 | | - ) |
275 | | - |
276 | | - |
277 | | -def remove_tabs_js(app: Sphinx, exc: Exception) -> None: |
278 | | - """Remove tabs.js from _static after build.""" |
279 | | - # Fix for sphinx-inline-tabs#18 |
280 | | - if app.builder.format == "html" and not exc: |
281 | | - tabs_js = pathlib.Path(app.builder.outdir) / "_static" / "tabs.js" |
282 | | - with contextlib.suppress(FileNotFoundError): |
283 | | - tabs_js.unlink() # When python 3.7 deprecated, use missing_ok=True |
284 | | - |
285 | | - |
286 | | -def setup(app: Sphinx) -> None: |
287 | | - """Configure Sphinx app hooks.""" |
288 | | - app.add_js_file("js/spa-nav.js", loading_method="defer") |
289 | | - app.connect("build-finished", remove_tabs_js) |
| 49 | + html_favicon="_static/favicon.ico", |
| 50 | + html_css_files=["css/custom.css"], |
| 51 | + html_extra_path=["manifest.json"], |
| 52 | + rediraffe_redirects="redirects.txt", |
| 53 | +) |
| 54 | +globals().update(conf) |
0 commit comments