stouputils 1.12.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 (138) hide show
  1. stouputils/__init__.py +40 -0
  2. stouputils/__init__.pyi +14 -0
  3. stouputils/__main__.py +81 -0
  4. stouputils/_deprecated.py +37 -0
  5. stouputils/_deprecated.pyi +12 -0
  6. stouputils/all_doctests.py +160 -0
  7. stouputils/all_doctests.pyi +46 -0
  8. stouputils/applications/__init__.py +22 -0
  9. stouputils/applications/__init__.pyi +2 -0
  10. stouputils/applications/automatic_docs.py +634 -0
  11. stouputils/applications/automatic_docs.pyi +106 -0
  12. stouputils/applications/upscaler/__init__.py +39 -0
  13. stouputils/applications/upscaler/__init__.pyi +3 -0
  14. stouputils/applications/upscaler/config.py +128 -0
  15. stouputils/applications/upscaler/config.pyi +18 -0
  16. stouputils/applications/upscaler/image.py +247 -0
  17. stouputils/applications/upscaler/image.pyi +109 -0
  18. stouputils/applications/upscaler/video.py +287 -0
  19. stouputils/applications/upscaler/video.pyi +60 -0
  20. stouputils/archive.py +344 -0
  21. stouputils/archive.pyi +67 -0
  22. stouputils/backup.py +488 -0
  23. stouputils/backup.pyi +109 -0
  24. stouputils/collections.py +244 -0
  25. stouputils/collections.pyi +86 -0
  26. stouputils/continuous_delivery/__init__.py +27 -0
  27. stouputils/continuous_delivery/__init__.pyi +5 -0
  28. stouputils/continuous_delivery/cd_utils.py +243 -0
  29. stouputils/continuous_delivery/cd_utils.pyi +129 -0
  30. stouputils/continuous_delivery/github.py +522 -0
  31. stouputils/continuous_delivery/github.pyi +162 -0
  32. stouputils/continuous_delivery/pypi.py +91 -0
  33. stouputils/continuous_delivery/pypi.pyi +43 -0
  34. stouputils/continuous_delivery/pyproject.py +147 -0
  35. stouputils/continuous_delivery/pyproject.pyi +67 -0
  36. stouputils/continuous_delivery/stubs.py +86 -0
  37. stouputils/continuous_delivery/stubs.pyi +39 -0
  38. stouputils/ctx.py +408 -0
  39. stouputils/ctx.pyi +211 -0
  40. stouputils/data_science/config/get.py +51 -0
  41. stouputils/data_science/config/set.py +125 -0
  42. stouputils/data_science/data_processing/image/__init__.py +66 -0
  43. stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
  44. stouputils/data_science/data_processing/image/axis_flip.py +58 -0
  45. stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
  46. stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
  47. stouputils/data_science/data_processing/image/blur.py +59 -0
  48. stouputils/data_science/data_processing/image/brightness.py +54 -0
  49. stouputils/data_science/data_processing/image/canny.py +110 -0
  50. stouputils/data_science/data_processing/image/clahe.py +92 -0
  51. stouputils/data_science/data_processing/image/common.py +30 -0
  52. stouputils/data_science/data_processing/image/contrast.py +53 -0
  53. stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
  54. stouputils/data_science/data_processing/image/denoise.py +378 -0
  55. stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
  56. stouputils/data_science/data_processing/image/invert.py +64 -0
  57. stouputils/data_science/data_processing/image/laplacian.py +60 -0
  58. stouputils/data_science/data_processing/image/median_blur.py +52 -0
  59. stouputils/data_science/data_processing/image/noise.py +59 -0
  60. stouputils/data_science/data_processing/image/normalize.py +65 -0
  61. stouputils/data_science/data_processing/image/random_erase.py +66 -0
  62. stouputils/data_science/data_processing/image/resize.py +69 -0
  63. stouputils/data_science/data_processing/image/rotation.py +80 -0
  64. stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
  65. stouputils/data_science/data_processing/image/sharpening.py +55 -0
  66. stouputils/data_science/data_processing/image/shearing.py +64 -0
  67. stouputils/data_science/data_processing/image/threshold.py +64 -0
  68. stouputils/data_science/data_processing/image/translation.py +71 -0
  69. stouputils/data_science/data_processing/image/zoom.py +83 -0
  70. stouputils/data_science/data_processing/image_augmentation.py +118 -0
  71. stouputils/data_science/data_processing/image_preprocess.py +183 -0
  72. stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
  73. stouputils/data_science/data_processing/technique.py +481 -0
  74. stouputils/data_science/dataset/__init__.py +45 -0
  75. stouputils/data_science/dataset/dataset.py +292 -0
  76. stouputils/data_science/dataset/dataset_loader.py +135 -0
  77. stouputils/data_science/dataset/grouping_strategy.py +296 -0
  78. stouputils/data_science/dataset/image_loader.py +100 -0
  79. stouputils/data_science/dataset/xy_tuple.py +696 -0
  80. stouputils/data_science/metric_dictionnary.py +106 -0
  81. stouputils/data_science/metric_utils.py +847 -0
  82. stouputils/data_science/mlflow_utils.py +206 -0
  83. stouputils/data_science/models/abstract_model.py +149 -0
  84. stouputils/data_science/models/all.py +85 -0
  85. stouputils/data_science/models/base_keras.py +765 -0
  86. stouputils/data_science/models/keras/all.py +38 -0
  87. stouputils/data_science/models/keras/convnext.py +62 -0
  88. stouputils/data_science/models/keras/densenet.py +50 -0
  89. stouputils/data_science/models/keras/efficientnet.py +60 -0
  90. stouputils/data_science/models/keras/mobilenet.py +56 -0
  91. stouputils/data_science/models/keras/resnet.py +52 -0
  92. stouputils/data_science/models/keras/squeezenet.py +233 -0
  93. stouputils/data_science/models/keras/vgg.py +42 -0
  94. stouputils/data_science/models/keras/xception.py +38 -0
  95. stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
  96. stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
  97. stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
  98. stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
  99. stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
  100. stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
  101. stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
  102. stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
  103. stouputils/data_science/models/keras_utils/visualizations.py +416 -0
  104. stouputils/data_science/models/model_interface.py +939 -0
  105. stouputils/data_science/models/sandbox.py +116 -0
  106. stouputils/data_science/range_tuple.py +234 -0
  107. stouputils/data_science/scripts/augment_dataset.py +77 -0
  108. stouputils/data_science/scripts/exhaustive_process.py +133 -0
  109. stouputils/data_science/scripts/preprocess_dataset.py +70 -0
  110. stouputils/data_science/scripts/routine.py +168 -0
  111. stouputils/data_science/utils.py +285 -0
  112. stouputils/decorators.py +595 -0
  113. stouputils/decorators.pyi +242 -0
  114. stouputils/image.py +441 -0
  115. stouputils/image.pyi +172 -0
  116. stouputils/installer/__init__.py +18 -0
  117. stouputils/installer/__init__.pyi +5 -0
  118. stouputils/installer/common.py +67 -0
  119. stouputils/installer/common.pyi +39 -0
  120. stouputils/installer/downloader.py +101 -0
  121. stouputils/installer/downloader.pyi +24 -0
  122. stouputils/installer/linux.py +144 -0
  123. stouputils/installer/linux.pyi +39 -0
  124. stouputils/installer/main.py +223 -0
  125. stouputils/installer/main.pyi +57 -0
  126. stouputils/installer/windows.py +136 -0
  127. stouputils/installer/windows.pyi +31 -0
  128. stouputils/io.py +486 -0
  129. stouputils/io.pyi +213 -0
  130. stouputils/parallel.py +453 -0
  131. stouputils/parallel.pyi +211 -0
  132. stouputils/print.py +527 -0
  133. stouputils/print.pyi +146 -0
  134. stouputils/py.typed +1 -0
  135. stouputils-1.12.1.dist-info/METADATA +179 -0
  136. stouputils-1.12.1.dist-info/RECORD +138 -0
  137. stouputils-1.12.1.dist-info/WHEEL +4 -0
  138. stouputils-1.12.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,634 @@
1
+ """ Sphinx documentation generation utilities.
2
+
3
+ This module provides a comprehensive set of utilities for automatically generating
4
+ and managing Sphinx documentation for Python projects. It handles the creation
5
+ of configuration files, index pages, version management, and HTML generation.
6
+
7
+ Example of usage:
8
+
9
+ .. code-block:: python
10
+
11
+ import stouputils as stp
12
+ from stouputils.applications import automatic_docs
13
+
14
+ if __name__ == "__main__":
15
+ automatic_docs.update_documentation(
16
+ root_path=stp.get_root_path(__file__, go_up=1),
17
+ project="stouputils",
18
+ author="Stoupy",
19
+ copyright="2025, Stoupy",
20
+ html_logo="https://avatars.githubusercontent.com/u/35665974",
21
+ html_favicon="https://avatars.githubusercontent.com/u/35665974",
22
+ html_theme="breeze", # Available themes: breeze, furo, pydata_sphinx_theme, sphinx_rtd_theme, or other you installed
23
+ github_user="Stoupy51",
24
+ github_repo="stouputils",
25
+ version="1.2.0",
26
+ skip_undocumented=True,
27
+ )
28
+
29
+ .. image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/applications/automatic_docs.gif
30
+ :alt: stouputils automatic_docs examples
31
+
32
+ Example of GitHub Actions workflow:
33
+
34
+ .. code-block:: yaml
35
+
36
+ name: documentation
37
+
38
+ on:
39
+ push:
40
+ tags:
41
+ - 'v*'
42
+ workflow_dispatch:
43
+
44
+ permissions:
45
+ contents: write
46
+
47
+ jobs:
48
+ docs:
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ - uses: actions/setup-python@v5
53
+ - name: Install dependencies
54
+ run: |
55
+ pip install stouputils[docs,data_science]
56
+ - name: Build version docs
57
+ run: |
58
+ python scripts/create_docs.py ${GITHUB_REF#refs/tags/v}
59
+ - name: Deploy to GitHub Pages
60
+ uses: peaceiris/actions-gh-pages@v3
61
+ with:
62
+ publish_branch: gh-pages
63
+ github_token: ${{ secrets.GITHUB_TOKEN }}
64
+ publish_dir: docs/build/html
65
+ keep_files: true
66
+ force_orphan: false
67
+ """
68
+ # Imports
69
+ import os
70
+ import shutil
71
+ import subprocess
72
+ import sys
73
+ from collections.abc import Callable
74
+ from typing import Any
75
+
76
+ from ..continuous_delivery import version_to_float
77
+ from ..decorators import LogLevels, handle_error, simple_cache
78
+ from ..io import clean_path, json_dump, super_open
79
+ from ..print import info
80
+
81
+ # Constants
82
+ REQUIREMENTS: list[str] = ["m2r2", "myst_parser"]
83
+ """ List of requirements for automatic_docs to work. """
84
+
85
+ # Functions
86
+ def check_dependencies(html_theme: str) -> None:
87
+ """ Check for each requirement if it is installed.
88
+
89
+ Args:
90
+ html_theme (str): HTML theme to use for the documentation, to check if it is installed (e.g. "breeze", "pydata_sphinx_theme", "furo", etc.)
91
+ """
92
+ import importlib
93
+ for requirement in REQUIREMENTS:
94
+ try:
95
+ importlib.import_module(requirement)
96
+ except ImportError as e:
97
+ requirements_str: str = " ".join(REQUIREMENTS)
98
+ raise ImportError(f"{requirement} is not installed. Please install it the following requirements to use automatic_docs: '{requirements_str}'") from e
99
+
100
+ if html_theme == "breeze":
101
+ html_theme = "sphinx_breeze_theme"
102
+ try:
103
+ importlib.import_module(html_theme)
104
+ except ImportError as e:
105
+ raise ImportError(f"{html_theme} is not installed. Please add it to your dependencies.") from e
106
+
107
+ def get_sphinx_conf_content(
108
+ project: str,
109
+ project_dir: str,
110
+ author: str,
111
+ current_version: str,
112
+ copyright: str,
113
+ html_logo: str,
114
+ html_favicon: str,
115
+ html_theme: str = "breeze",
116
+ github_user: str = "",
117
+ github_repo: str = "",
118
+ version_list: list[str] | None = None,
119
+ skip_undocumented: bool = True,
120
+ ) -> str:
121
+ """ Get the content of the Sphinx configuration file.
122
+
123
+ Args:
124
+ project (str): Name of the project
125
+ project_dir (str): Path to the project directory
126
+ author (str): Author of the project
127
+ current_version (str): Current version
128
+ copyright (str): Copyright information
129
+ html_logo (str): URL to the logo
130
+ html_favicon (str): URL to the favicon
131
+ github_user (str): GitHub username
132
+ github_repo (str): GitHub repository name
133
+ version_list (list[str] | None): List of versions. Defaults to None
134
+ skip_undocumented (bool): Whether to skip undocumented members. Defaults to True
135
+
136
+ Returns:
137
+ str: Content of the Sphinx configuration file
138
+ """
139
+ parent_of_project_dir: str = clean_path(os.path.dirname(project_dir))
140
+ conf_content: str = f"""
141
+ # Imports
142
+ import sys
143
+ from typing import Any
144
+
145
+ # Add project_dir directory to Python path for module discovery
146
+ sys.path.insert(0, "{parent_of_project_dir}")
147
+
148
+ # Project information
149
+ project: str = "{project}"
150
+ copyright: str = "{copyright}"
151
+ author: str = "{author}"
152
+ release: str = "{current_version}"
153
+
154
+ # General configuration
155
+ extensions: list[str] = [
156
+ # Sphinx's own extensions
157
+ "sphinx.ext.githubpages",
158
+ "sphinx.ext.autodoc",
159
+ "sphinx.ext.napoleon",
160
+ "sphinx.ext.extlinks",
161
+ "sphinx.ext.intersphinx",
162
+ "sphinx.ext.mathjax",
163
+ "sphinx.ext.todo",
164
+ "sphinx.ext.viewcode",
165
+
166
+ # External stuff
167
+ "myst_parser",
168
+ "sphinx_copybutton",
169
+ "sphinx_design",
170
+ "sphinx_treeview",
171
+ ]
172
+
173
+ myst_enable_extensions = [
174
+ "colon_fence",
175
+ "deflist",
176
+ "fieldlist",
177
+ "substitution",
178
+ ]
179
+ myst_heading_anchors = 3
180
+ todo_include_todos = True
181
+
182
+ copybutton_exclude = ".linenos, .gp"
183
+ copybutton_selector = ":not(.prompt) > div.highlight pre"
184
+
185
+ templates_path: list[str] = ["_templates"]
186
+ exclude_patterns: list[str] = []
187
+
188
+ # HTML output options
189
+ html_theme: str = "{html_theme}"
190
+ html_static_path: list[str] = ["_static"]
191
+ html_css_files: list[str] = ["custom.css"]
192
+ html_logo: str = "{html_logo}"
193
+ html_title: str = "{project}"
194
+ html_favicon: str = "{html_favicon}"
195
+
196
+ # Theme options
197
+ html_theme_options: dict[str, Any] = {{
198
+ "navigation_with_keys": True,
199
+ }}
200
+ """
201
+ # Create base html_context dictionary
202
+ html_context: dict[str, Any] = {
203
+ "display_github": True,
204
+ "github_user": github_user,
205
+ "github_repo": github_repo,
206
+ "github_version": "main",
207
+ "conf_py_path": "/docs/source/",
208
+ "source_suffix": [".rst", ".md"],
209
+ "default_mode": "dark",
210
+ }
211
+
212
+ # Add version selector if versions are provided
213
+ if version_list and current_version:
214
+ html_context.update({
215
+ "versions": version_list,
216
+ "current_version": current_version,
217
+ })
218
+ html_context_str: str = json_dump(html_context, max_level=1).replace("true", "True").replace("false", "False")
219
+
220
+ conf_content += f"""
221
+ html_context = {html_context_str}
222
+
223
+ # Autodoc settings
224
+ autodoc_default_options: dict[str, bool | str] = {{
225
+ "members": True,
226
+ "member-order": "bysource",
227
+ "special-members": False,
228
+ "undoc-members": False,
229
+ "private-members": True,
230
+ "show-inheritance": True,
231
+ "ignore-module-all": True,
232
+ "exclude-members": "__weakref__",
233
+ }}
234
+
235
+ # Tell autodoc to prefer source code over installed package
236
+ autodoc_mock_imports = []
237
+ always_document_param_types = True
238
+ add_module_names = False
239
+
240
+ # Prevent social media cards and images from being used
241
+ html_meta = globals().get("html_meta", {{}})
242
+ html_meta.pop("image", None)
243
+ html_context = globals().get("html_context", {{}})
244
+ html_context.pop("image", None)
245
+ html_context.pop("social_card", None)
246
+ ogp_social_cards = {{"enable": False}}
247
+ ogp_site_url = ""
248
+ """
249
+
250
+ if skip_undocumented:
251
+ conf_content += """
252
+ # Only document items with docstrings
253
+ def skip_undocumented(app: Any, what: str, name: str, obj: Any, skip: bool, *args: Any, **kwargs: Any) -> bool:
254
+ if not obj.__doc__:
255
+ return True
256
+ return skip
257
+
258
+ def setup(app: Any) -> None:
259
+ app.connect("autodoc-skip-member", skip_undocumented)
260
+ """
261
+ return conf_content
262
+
263
+ @simple_cache()
264
+ def get_versions_from_github(github_user: str, github_repo: str, recent_minor_versions: int = 2) -> list[str]:
265
+ """ Get list of versions from GitHub gh-pages branch.
266
+ Only shows detailed versions for the last N minor versions, and keeps only
267
+ the latest patch version for older minor versions.
268
+
269
+ Args:
270
+ github_user (str): GitHub username
271
+ github_repo (str): GitHub repository name
272
+ recent_minor_versions (int): Number of recent minor versions to show all patches for (-1 for all).
273
+
274
+ Returns:
275
+ list[str]: List of versions, with 'latest' as first element
276
+ """
277
+ import requests
278
+ version_list: list[str] = []
279
+ try:
280
+ response = requests.get(f"https://api.github.com/repos/{github_user}/{github_repo}/contents?ref=gh-pages")
281
+ if response.status_code == 200:
282
+ contents: list[dict[str, str]] = response.json()
283
+ all_versions: list[str] = sorted([
284
+ d["name"].replace("v", "")
285
+ for d in contents
286
+ if d["type"] == "dir" and d["name"].startswith("v")
287
+ ], key=version_to_float, reverse=True
288
+ )
289
+
290
+ # Group versions by major.minor
291
+ from collections import defaultdict
292
+ minor_versions: dict[str, list[str]] = defaultdict(list)
293
+ for version in all_versions:
294
+ parts = version.split(".")
295
+ if len(parts) >= 2:
296
+ minor_key = f"{parts[0]}.{parts[1]}"
297
+ minor_versions[minor_key].append(version)
298
+
299
+ # Get the sorted minor version keys
300
+ sorted_minors = sorted(minor_versions.keys(), key=version_to_float, reverse=True)
301
+
302
+ # Build final version list
303
+ final_versions: list[str] = []
304
+ for i, minor_key in enumerate(sorted_minors):
305
+ if recent_minor_versions == -1 or i < recent_minor_versions:
306
+ # Keep all patch versions for the recent minor versions
307
+ final_versions.extend(minor_versions[minor_key])
308
+ else:
309
+ # Keep only the latest patch version for older minor versions
310
+ final_versions.append(minor_versions[minor_key][0])
311
+
312
+ version_list = ["latest", *final_versions]
313
+ except Exception as e:
314
+ info(f"Failed to get versions from GitHub: {e}")
315
+ version_list = ["latest"]
316
+ return version_list
317
+
318
+ def markdown_to_rst(markdown_content: str) -> str:
319
+ """ Convert markdown content to RST format.
320
+
321
+ Args:
322
+ markdown_content (str): Markdown content
323
+
324
+ Returns:
325
+ str: RST content
326
+ """
327
+ if not markdown_content:
328
+ return ""
329
+
330
+ # Convert markdown to RST and return it
331
+ import m2r2 # type: ignore
332
+ return m2r2.convert(markdown_content) # type: ignore
333
+
334
+ def generate_index_rst(
335
+ readme_path: str,
336
+ index_path: str,
337
+ project: str,
338
+ github_user: str,
339
+ github_repo: str,
340
+ get_versions_function: Callable[[str, str, int], list[str]] = get_versions_from_github,
341
+ recent_minor_versions: int = 2,
342
+ ) -> None:
343
+ """ Generate index.rst from README.md content.
344
+
345
+ Args:
346
+ readme_path (str): Path to the README.md file
347
+ index_path (str): Path where index.rst should be created
348
+ project (str): Name of the project
349
+ github_user (str): GitHub username
350
+ github_repo (str): GitHub repository name
351
+ get_versions_function (Callable[[str, str, int], list[str]]): Function to get versions from GitHub
352
+ recent_minor_versions (int): Number of recent minor versions to show all patches for. Defaults to 2
353
+ """
354
+ # Read README content
355
+ with open(readme_path, encoding="utf-8") as f:
356
+ readme_content: str = f.read()
357
+
358
+ # Generate version selector
359
+ version_selector: str = "\n\n**Versions**: "
360
+
361
+ # Get versions from GitHub
362
+ version_list: list[str] = get_versions_function(github_user, github_repo, recent_minor_versions)
363
+
364
+ # Create version links
365
+ version_links: list[str] = []
366
+ for version in version_list:
367
+ if version == "latest":
368
+ version_links.append("`latest <../latest/>`_")
369
+ else:
370
+ version_links.append(f"`v{version} <../v{version}/>`_")
371
+ version_selector += ", ".join(version_links)
372
+
373
+ # Generate module documentation section
374
+ project_module: str = project.lower()
375
+ module_docs: str = f"""
376
+ .. toctree::
377
+ :maxdepth: 10
378
+
379
+ modules/{project_module}
380
+ """
381
+ module_docs = markdown_to_rst(f"""
382
+ Here is the complete unsorted documentation for all modules in the {project} project.<br>
383
+ Prefer to use the search button at the top to find what you need!
384
+ """) + module_docs
385
+
386
+ # Convert markdown to RST
387
+ rst_content: str = f"""
388
+ ✨ Welcome to {project.capitalize()} Documentation ✨
389
+ {'=' * 100}
390
+ {version_selector}
391
+
392
+ {markdown_to_rst(readme_content)}
393
+
394
+ 📖 Module Documentation
395
+ {'-' * 100}
396
+ {module_docs}
397
+ """
398
+
399
+ # Write the RST file
400
+ with open(index_path, "w", encoding="utf-8") as f:
401
+ f.write(rst_content)
402
+
403
+ def generate_documentation(
404
+ source_dir: str,
405
+ modules_dir: str,
406
+ project_dir: str,
407
+ build_dir: str,
408
+ ) -> None:
409
+ """ Generate documentation using Sphinx.
410
+
411
+ Args:
412
+ source_dir (str): Source directory
413
+ modules_dir (str): Modules directory
414
+ project_dir (str): Project directory
415
+ build_dir (str): Build directory
416
+ """
417
+ # Generate module documentation using sphinx-apidoc
418
+ subprocess.run([
419
+ sys.executable,
420
+ "-m", "sphinx.ext.apidoc",
421
+ "-o", modules_dir,
422
+ "-f", "-e", "-M",
423
+ "--no-toc",
424
+ "-P",
425
+ "--implicit-namespaces",
426
+ "--module-first",
427
+ project_dir,
428
+ ], check=True)
429
+
430
+ # Build HTML documentation
431
+ subprocess.run([
432
+ sys.executable,
433
+ "-m", "sphinx",
434
+ "-b", "html",
435
+ "-a",
436
+ source_dir,
437
+ build_dir,
438
+ ], check=True)
439
+
440
+ def generate_redirect_html(filepath: str) -> None:
441
+ """ Generate HTML content for redirect page.
442
+
443
+ Args:
444
+ filepath (str): Path to the file where the HTML content should be written
445
+ """
446
+ with super_open(filepath, "w", encoding="utf-8") as f:
447
+ f.write("""<!DOCTYPE html>
448
+ <html lang="en">
449
+ <head>
450
+ <meta charset="UTF-8">
451
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
452
+ <meta http-equiv="refresh" content="0;url=./latest/">
453
+ <title>Redirecting...</title>
454
+ </head>
455
+ <body>
456
+ <p>If you are not redirected automatically, <a href="./latest/">click here</a>.</p>
457
+ </body>
458
+ </html>
459
+ """)
460
+
461
+ @handle_error(error_log=LogLevels.WARNING_TRACEBACK)
462
+ def update_documentation(
463
+ root_path: str,
464
+ project: str,
465
+ project_dir: str = "",
466
+ author: str = "Author",
467
+ copyright: str = "2025, Author",
468
+ html_logo: str = "",
469
+ html_favicon: str = "",
470
+ html_theme: str = "breeze",
471
+ github_user: str = "",
472
+ github_repo: str = "",
473
+ version: str | None = None,
474
+ skip_undocumented: bool = True,
475
+ recent_minor_versions: int = 2,
476
+
477
+ get_versions_function: Callable[[str, str, int], list[str]] = get_versions_from_github,
478
+ generate_index_function: Callable[..., None] = generate_index_rst,
479
+ generate_docs_function: Callable[..., None] = generate_documentation,
480
+ generate_redirect_function: Callable[[str], None] = generate_redirect_html,
481
+ get_conf_content_function: Callable[..., str] = get_sphinx_conf_content
482
+ ) -> None:
483
+ """ Update the Sphinx documentation.
484
+
485
+ Args:
486
+ root_path (str): Root path of the project
487
+ project (str): Name of the project
488
+ project_dir (str): Path to the project directory (to be used with generate_docs_function)
489
+ author (str): Author of the project
490
+ copyright (str): Copyright information
491
+ html_logo (str): URL to the logo
492
+ html_favicon (str): URL to the favicon
493
+ html_theme (str): Theme to use for the documentation. Defaults to "breeze"
494
+ github_user (str): GitHub username
495
+ github_repo (str): GitHub repository name
496
+ version (str | None): Version to build documentation for (e.g. "1.0.0", defaults to "latest")
497
+ skip_undocumented (bool): Whether to skip undocumented members. Defaults to True
498
+ recent_minor_versions (int): Number of recent minor versions to show all patches for. Defaults to 2
499
+
500
+ get_versions_function (Callable[[str, str, int], list[str]]): Function to get versions from GitHub
501
+ generate_index_function (Callable[..., None]): Function to generate index.rst
502
+ generate_docs_function (Callable[..., None]): Function to generate documentation
503
+ generate_redirect_function (Callable[[str], None]): Function to create redirect file
504
+ get_conf_content_function (Callable[..., str]): Function to get Sphinx conf.py content
505
+ """
506
+ check_dependencies(html_theme)
507
+
508
+ # Setup paths
509
+ root_path = clean_path(root_path)
510
+ docs_dir: str = f"{root_path}/docs"
511
+ source_dir: str = f"{docs_dir}/source"
512
+ modules_dir: str = f"{source_dir}/modules"
513
+ static_dir: str = f"{source_dir}/_static"
514
+ templates_dir: str = f"{source_dir}/_templates"
515
+ html_dir: str = f"{docs_dir}/build/html"
516
+
517
+ # Remove "v" from version if it is a string (just in case)
518
+ version = version.replace("v", "") if isinstance(version, str) else version
519
+
520
+ # Modify build directory if version is specified
521
+ latest_dir: str = f"{html_dir}/latest"
522
+ build_dir: str = latest_dir if not version else f"{html_dir}/v{version}"
523
+
524
+ # Create directories if they don't exist
525
+ for dir in [modules_dir, static_dir, templates_dir]:
526
+ os.makedirs(dir, exist_ok=True)
527
+
528
+ # Create custom CSS file to reduce heading sizes
529
+ custom_css_path: str = f"{static_dir}/custom.css"
530
+ with super_open(custom_css_path, "w") as f:
531
+ f.write("""
532
+ /* Custom CSS for Sphinx documentation */
533
+ /* Reduce heading sizes */
534
+ h1 { font-size: 2.0em !important; }
535
+ h2 { font-size: 1.6em !important; }
536
+ h3 { font-size: 1.4em !important; }
537
+ h4 { font-size: 1.2em !important; }
538
+ h5 { font-size: 1.0em !important; }
539
+ h6 { font-size: 0.9em !important; }
540
+
541
+ /* Gradient animation keyframes */
542
+ @keyframes shine-slide {
543
+ 0% { background-position: -200% center; }
544
+ 100% { background-position: 200% center; }
545
+ }
546
+
547
+ /* On hover animation for various elements */
548
+ a, h1, h2, h3, h4, h5, h6, .admonition {
549
+ transition: transform 0.3s;
550
+ position: relative;
551
+ }
552
+
553
+ a:hover, h1:hover, h2:hover, h3:hover, h4:hover, h5:hover, h6:hover, .admonition:hover {
554
+ transform: scale(1.05);
555
+ }
556
+ a:hover {
557
+ background: linear-gradient(
558
+ 110deg,
559
+ currentColor 0%,
560
+ currentColor 40%,
561
+ white 50%,
562
+ currentColor 60%,
563
+ currentColor 100%
564
+ );
565
+ background-size: 200% 100%;
566
+ background-clip: text;
567
+ -webkit-background-clip: text;
568
+ -webkit-text-fill-color: transparent;
569
+ animation: shine-slide 3.5s linear infinite;
570
+ }
571
+ """)
572
+
573
+ # Generate index.rst from README.md
574
+ readme_path: str = f"{root_path}/README.md"
575
+ index_path: str = f"{source_dir}/index.rst"
576
+ generate_index_function(
577
+ readme_path=readme_path,
578
+ index_path=index_path,
579
+ project=project,
580
+ github_user=github_user,
581
+ github_repo=github_repo,
582
+ get_versions_function=get_versions_function,
583
+ recent_minor_versions=recent_minor_versions,
584
+ )
585
+
586
+ # Clean up old module documentation
587
+ if os.path.exists(modules_dir):
588
+ shutil.rmtree(modules_dir)
589
+ os.makedirs(modules_dir, exist_ok=True)
590
+
591
+ # Get versions and current version for conf.py
592
+ version_list: list[str] = get_versions_function(github_user, github_repo, recent_minor_versions)
593
+ current_version: str = version if version else "latest"
594
+
595
+ # Generate conf.py
596
+ conf_path: str = f"{source_dir}/conf.py"
597
+ conf_content: str = get_conf_content_function(
598
+ project=project,
599
+ project_dir=project_dir,
600
+ author=author,
601
+ current_version=current_version,
602
+ copyright=copyright,
603
+ html_logo=html_logo,
604
+ html_favicon=html_favicon,
605
+ html_theme=html_theme,
606
+ github_user=github_user,
607
+ github_repo=github_repo,
608
+ version_list=version_list,
609
+ skip_undocumented=skip_undocumented,
610
+ )
611
+ with open(conf_path, "w", encoding="utf-8") as f:
612
+ f.write(conf_content)
613
+
614
+ # Generate documentation
615
+ generate_docs_function(
616
+ source_dir=source_dir,
617
+ modules_dir=modules_dir,
618
+ project_dir=project_dir if project_dir else f"{root_path}/{project}",
619
+ build_dir=build_dir,
620
+ )
621
+
622
+ # Add index.html to the build directory that redirects to the latest version
623
+ generate_redirect_function(f"{html_dir}/index.html")
624
+
625
+ # If version is specified, copy the build directory to latest too
626
+ # This is useful for GitHub Actions to prevent re-building the documentation from scratch without the version
627
+ if version:
628
+ if os.path.exists(latest_dir):
629
+ shutil.rmtree(latest_dir)
630
+ shutil.copytree(build_dir, latest_dir, dirs_exist_ok=True)
631
+
632
+ info("Documentation updated successfully!")
633
+ info(f"You can view the documentation by opening {build_dir}/index.html")
634
+