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.
- stouputils/__init__.py +40 -0
- stouputils/__init__.pyi +14 -0
- stouputils/__main__.py +81 -0
- stouputils/_deprecated.py +37 -0
- stouputils/_deprecated.pyi +12 -0
- stouputils/all_doctests.py +160 -0
- stouputils/all_doctests.pyi +46 -0
- stouputils/applications/__init__.py +22 -0
- stouputils/applications/__init__.pyi +2 -0
- stouputils/applications/automatic_docs.py +634 -0
- stouputils/applications/automatic_docs.pyi +106 -0
- stouputils/applications/upscaler/__init__.py +39 -0
- stouputils/applications/upscaler/__init__.pyi +3 -0
- stouputils/applications/upscaler/config.py +128 -0
- stouputils/applications/upscaler/config.pyi +18 -0
- stouputils/applications/upscaler/image.py +247 -0
- stouputils/applications/upscaler/image.pyi +109 -0
- stouputils/applications/upscaler/video.py +287 -0
- stouputils/applications/upscaler/video.pyi +60 -0
- stouputils/archive.py +344 -0
- stouputils/archive.pyi +67 -0
- stouputils/backup.py +488 -0
- stouputils/backup.pyi +109 -0
- stouputils/collections.py +244 -0
- stouputils/collections.pyi +86 -0
- stouputils/continuous_delivery/__init__.py +27 -0
- stouputils/continuous_delivery/__init__.pyi +5 -0
- stouputils/continuous_delivery/cd_utils.py +243 -0
- stouputils/continuous_delivery/cd_utils.pyi +129 -0
- stouputils/continuous_delivery/github.py +522 -0
- stouputils/continuous_delivery/github.pyi +162 -0
- stouputils/continuous_delivery/pypi.py +91 -0
- stouputils/continuous_delivery/pypi.pyi +43 -0
- stouputils/continuous_delivery/pyproject.py +147 -0
- stouputils/continuous_delivery/pyproject.pyi +67 -0
- stouputils/continuous_delivery/stubs.py +86 -0
- stouputils/continuous_delivery/stubs.pyi +39 -0
- stouputils/ctx.py +408 -0
- stouputils/ctx.pyi +211 -0
- stouputils/data_science/config/get.py +51 -0
- stouputils/data_science/config/set.py +125 -0
- stouputils/data_science/data_processing/image/__init__.py +66 -0
- stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
- stouputils/data_science/data_processing/image/axis_flip.py +58 -0
- stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
- stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
- stouputils/data_science/data_processing/image/blur.py +59 -0
- stouputils/data_science/data_processing/image/brightness.py +54 -0
- stouputils/data_science/data_processing/image/canny.py +110 -0
- stouputils/data_science/data_processing/image/clahe.py +92 -0
- stouputils/data_science/data_processing/image/common.py +30 -0
- stouputils/data_science/data_processing/image/contrast.py +53 -0
- stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
- stouputils/data_science/data_processing/image/denoise.py +378 -0
- stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
- stouputils/data_science/data_processing/image/invert.py +64 -0
- stouputils/data_science/data_processing/image/laplacian.py +60 -0
- stouputils/data_science/data_processing/image/median_blur.py +52 -0
- stouputils/data_science/data_processing/image/noise.py +59 -0
- stouputils/data_science/data_processing/image/normalize.py +65 -0
- stouputils/data_science/data_processing/image/random_erase.py +66 -0
- stouputils/data_science/data_processing/image/resize.py +69 -0
- stouputils/data_science/data_processing/image/rotation.py +80 -0
- stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
- stouputils/data_science/data_processing/image/sharpening.py +55 -0
- stouputils/data_science/data_processing/image/shearing.py +64 -0
- stouputils/data_science/data_processing/image/threshold.py +64 -0
- stouputils/data_science/data_processing/image/translation.py +71 -0
- stouputils/data_science/data_processing/image/zoom.py +83 -0
- stouputils/data_science/data_processing/image_augmentation.py +118 -0
- stouputils/data_science/data_processing/image_preprocess.py +183 -0
- stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
- stouputils/data_science/data_processing/technique.py +481 -0
- stouputils/data_science/dataset/__init__.py +45 -0
- stouputils/data_science/dataset/dataset.py +292 -0
- stouputils/data_science/dataset/dataset_loader.py +135 -0
- stouputils/data_science/dataset/grouping_strategy.py +296 -0
- stouputils/data_science/dataset/image_loader.py +100 -0
- stouputils/data_science/dataset/xy_tuple.py +696 -0
- stouputils/data_science/metric_dictionnary.py +106 -0
- stouputils/data_science/metric_utils.py +847 -0
- stouputils/data_science/mlflow_utils.py +206 -0
- stouputils/data_science/models/abstract_model.py +149 -0
- stouputils/data_science/models/all.py +85 -0
- stouputils/data_science/models/base_keras.py +765 -0
- stouputils/data_science/models/keras/all.py +38 -0
- stouputils/data_science/models/keras/convnext.py +62 -0
- stouputils/data_science/models/keras/densenet.py +50 -0
- stouputils/data_science/models/keras/efficientnet.py +60 -0
- stouputils/data_science/models/keras/mobilenet.py +56 -0
- stouputils/data_science/models/keras/resnet.py +52 -0
- stouputils/data_science/models/keras/squeezenet.py +233 -0
- stouputils/data_science/models/keras/vgg.py +42 -0
- stouputils/data_science/models/keras/xception.py +38 -0
- stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
- stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
- stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
- stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
- stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
- stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
- stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
- stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
- stouputils/data_science/models/keras_utils/visualizations.py +416 -0
- stouputils/data_science/models/model_interface.py +939 -0
- stouputils/data_science/models/sandbox.py +116 -0
- stouputils/data_science/range_tuple.py +234 -0
- stouputils/data_science/scripts/augment_dataset.py +77 -0
- stouputils/data_science/scripts/exhaustive_process.py +133 -0
- stouputils/data_science/scripts/preprocess_dataset.py +70 -0
- stouputils/data_science/scripts/routine.py +168 -0
- stouputils/data_science/utils.py +285 -0
- stouputils/decorators.py +595 -0
- stouputils/decorators.pyi +242 -0
- stouputils/image.py +441 -0
- stouputils/image.pyi +172 -0
- stouputils/installer/__init__.py +18 -0
- stouputils/installer/__init__.pyi +5 -0
- stouputils/installer/common.py +67 -0
- stouputils/installer/common.pyi +39 -0
- stouputils/installer/downloader.py +101 -0
- stouputils/installer/downloader.pyi +24 -0
- stouputils/installer/linux.py +144 -0
- stouputils/installer/linux.pyi +39 -0
- stouputils/installer/main.py +223 -0
- stouputils/installer/main.pyi +57 -0
- stouputils/installer/windows.py +136 -0
- stouputils/installer/windows.pyi +31 -0
- stouputils/io.py +486 -0
- stouputils/io.pyi +213 -0
- stouputils/parallel.py +453 -0
- stouputils/parallel.pyi +211 -0
- stouputils/print.py +527 -0
- stouputils/print.pyi +146 -0
- stouputils/py.typed +1 -0
- stouputils-1.12.1.dist-info/METADATA +179 -0
- stouputils-1.12.1.dist-info/RECORD +138 -0
- stouputils-1.12.1.dist-info/WHEEL +4 -0
- 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
|
+
|