11"""Sphinx documentation configuration file."""
22
33from datetime import datetime
4+ import fnmatch
5+ import hashlib
46import os
57import pathlib
68import shutil
79import subprocess
10+ import zipfile
811
912import sphinx
1013from sphinx .util import logging
130133 rst_epilog += links_file .read ()
131134
132135
133- # Read available Docker images for Windows and Linux
134- DOCKER_DIR = pathlib .Path (__file__ ).parent .parent .parent .absolute () / "docker"
135- WINDOWS_IMAGES , UBUNTU_IMAGES = [DOCKER_DIR / path for path in ["windows" , "linux/ubuntu" ]]
136-
137-
138- def get_images_directories_from_path (path ):
139- """Get all the Docker images present in the retrieved Path."""
140- images = [
141- folder .name for folder in path .glob ("**/*" ) if folder .name != path .name and (folder / "Dockerfile" ).exists ()
142- ] or ["No images available." ]
143- images .sort ()
144- return images
145-
146-
147136# -- Declare the Jinja context -----------------------------------------------
148137BUILD_API = True if os .environ .get ("BUILD_API" , "true" ) == "true" else False
149138if not BUILD_API :
@@ -203,21 +192,106 @@ def get_images_directories_from_path(path):
203192
204193
205194# -- Jinja context configuration ---------------------------------------------
195+
196+
197+ def zip_directory (directory_path : pathlib .Path , zip_filename : pathlib .Path , ignore_patterns = None ):
198+ """Compress a directory using ZIP.
199+
200+ Parameters
201+ ----------
202+ directory_path : ~pathlib.Path
203+ Directory to compress.
204+ zip_filename : ~pathlib.Path
205+ Output file path.
206+ ignore_patterns : list
207+ List of Unix-like pattern to ignore.
208+
209+ """
210+ if ignore_patterns is None :
211+ ignore_patterns = []
212+
213+ if not zip_filename .suffix == ".zip" :
214+ zip_filename = zip_filename .with_suffix (".zip" )
215+
216+ with zipfile .ZipFile (zip_filename , "w" , zipfile .ZIP_DEFLATED ) as zipf :
217+ for file_path in directory_path .rglob ("*" ):
218+ if file_path .is_file ():
219+ if any (fnmatch .fnmatch (file_path .relative_to (directory_path ), pattern ) for pattern in ignore_patterns ):
220+ continue
221+
222+ relative_path = file_path .relative_to (directory_path )
223+ zipf .write (file_path , relative_path )
224+
225+
226+ def get_sha256_from_file (filepath : pathlib .Path ):
227+ """Compute the SHA-256 for a file.
228+
229+ Parameters
230+ ----------
231+ filepath : ~pathlib.Path
232+ Desired file.
233+
234+ Returns
235+ -------
236+ str
237+ String representing the SHA-256 hash.
238+
239+ """
240+ sha256_hash = hashlib .sha256 ()
241+ with open (filepath , "rb" ) as file :
242+ while chunk := file .read (8192 ):
243+ sha256_hash .update (chunk )
244+ return sha256_hash .hexdigest ()
245+
246+
247+ def get_file_size_in_mb (file_path ):
248+ """
249+ Compute the size of a file in megabytes.
250+
251+ Parameters
252+ ----------
253+ file_path : str or Path
254+ The path to the file whose size is to be computed.
255+
256+ Returns
257+ -------
258+ float
259+ The size of the file in megabytes.
260+
261+ Raises
262+ ------
263+ FileNotFoundError
264+ If the file does not exist.
265+ OSError
266+ If an OS-related error occurs while accessing the file.
267+
268+ """
269+ path = pathlib .Path (file_path )
270+
271+ if not path .is_file ():
272+ raise FileNotFoundError (f"The file at { file_path } does not exist." )
273+
274+ file_size_bytes = path .stat ().st_size
275+ return file_size_bytes / (1024 * 1024 )
276+
277+
278+ ARTIFACTS_PATH = pathlib .Path ().parent / "_static" / "artifacts"
279+ ARTIFACTS_WHEEL = ARTIFACTS_PATH / f"{ project .replace ('-' , '_' )} -{ version } -py3-none-any.whl"
280+ ARTIFACTS_SDIST = ARTIFACTS_PATH / f"{ project .replace ('-' , '_' )} -{ version } .tar.gz"
281+
206282jinja_contexts = {
207- "docker_images" : {
208- "windows_images" : get_images_directories_from_path (WINDOWS_IMAGES ),
209- "linux_images" : get_images_directories_from_path (UBUNTU_IMAGES ),
210- },
211- "install_guide" : {
212- "version" : f"v{ version } " if not version .endswith ("dev0" ) else "main" ,
213- },
283+ "install_guide" : {"stk_version" : "12.9.0" },
214284 "main_toctree" : {
215285 "build_api" : BUILD_API ,
216286 "build_examples" : BUILD_EXAMPLES ,
217287 },
218288 "artifacts" : {
219- "wheels" : f"{ project .replace ('-' , '_' )} -{ version } -py3-none-any.whl" ,
220- "source" : f"{ project .replace ('-' , '_' )} -{ version } .tar.gz" ,
289+ "wheels" : ARTIFACTS_WHEEL .name ,
290+ "wheels_size" : f"{ get_file_size_in_mb (ARTIFACTS_WHEEL ):.2f} MB" ,
291+ "wheels_hash" : get_sha256_from_file (ARTIFACTS_WHEEL ),
292+ "source" : ARTIFACTS_SDIST .name ,
293+ "source_size" : f"{ get_file_size_in_mb (ARTIFACTS_SDIST ):.2f} MB" ,
294+ "source_hash" : get_sha256_from_file (ARTIFACTS_SDIST ),
221295 "platforms" : ["Windows" , "Linux" ],
222296 },
223297}
@@ -241,6 +315,7 @@ def get_images_directories_from_path(path):
241315 # Requires sign-in
242316 f"https://github.com/{ user_repo } /*" ,
243317 "https://support.agi.com/3d-models" ,
318+ "https://support.agi.com/downloads" ,
244319]
245320
246321# -- MyST Sphinx configuration -----------------------------------------------
@@ -249,7 +324,7 @@ def get_images_directories_from_path(path):
249324# -- Sphinx application setup ------------------------------------------------
250325
251326
252- def copy_examples_files_to_source_dir (app : sphinx .application .Sphinx ):
327+ def copy_docker_files_to_static_dir (app : sphinx .application .Sphinx ):
253328 """
254329 Copy the examples directory to the source directory of the documentation.
255330
@@ -259,27 +334,36 @@ def copy_examples_files_to_source_dir(app: sphinx.application.Sphinx):
259334 Sphinx application instance containing the all the doc build configuration.
260335
261336 """
262- SOURCE_EXAMPLES = pathlib .Path (app .srcdir ) / "examples"
263- if not SOURCE_EXAMPLES .exists ():
264- SOURCE_EXAMPLES .mkdir (parents = True , exist_ok = True )
337+ SOURCE_DIR = pathlib .Path (app .srcdir )
338+ DOCKER_DIR = SOURCE_DIR .parent .parent / "docker"
339+ STATIC_DOCKER_DIR = SOURCE_DIR / "_static" / "docker"
340+ if not STATIC_DOCKER_DIR .exists ():
341+ STATIC_DOCKER_DIR .mkdir ()
265342
266- EXAMPLES_DIRECTORY = SOURCE_EXAMPLES .parent .parent .parent / "examples"
343+ COMPRESSED_DOCKER_WINDOWS_IMAGES = STATIC_DOCKER_DIR / "windows.zip"
344+ COMPRESSED_DOCKER_LINUX_IMAGES = STATIC_DOCKER_DIR / "linux.zip"
267345
268- all_examples = list (EXAMPLES_DIRECTORY .glob ("*.py" ))
269- examples = [file for file in all_examples if f"{ file .name } " not in exclude_examples ]
346+ logger = logging .getLogger (__name__ )
270347
271- print (f"BUILDER: { app .builder .name } " )
348+ logger .info (f"\n Compressing Docker images..." )
349+ zip_directory (DOCKER_DIR / "windows" , COMPRESSED_DOCKER_WINDOWS_IMAGES , ignore_patterns = ["*.tgz" ])
350+ zip_directory (DOCKER_DIR / "linux" , COMPRESSED_DOCKER_LINUX_IMAGES , ignore_patterns = ["*.tgz" ])
272351
273- for file in status_iterator (
274- examples ,
275- f"Copying example to doc/source/examples/" ,
276- "green" ,
277- len (examples ),
278- verbosity = 1 ,
279- stringify_func = (lambda file : file .name ),
280- ):
281- destination_file = SOURCE_EXAMPLES / file .name
282- destination_file .write_text (file .read_text (encoding = "utf-8" ), encoding = "utf-8" )
352+ # Add the new files and their information to the Jinja context. This
353+ # operation can not be performed outside of this function since the compressed files do not yet exist.
354+
355+ DOCKER_RECIPES = SOURCE_DIR / "_static" / "docker"
356+ DOCKER_RECIPES_WINDOWS = DOCKER_RECIPES / "windows.zip"
357+ DOCKER_RECIPES_LINUX = DOCKER_RECIPES / "linux.zip"
358+
359+ jinja_contexts ["docker_images" ] = {
360+ "docker_recipes_windows" : DOCKER_RECIPES_WINDOWS .name ,
361+ "docker_recipes_windows_size" : f"{ get_file_size_in_mb (DOCKER_RECIPES_WINDOWS ):.2f} MB" ,
362+ "docker_recipes_windows_hash" : f"{ get_sha256_from_file (DOCKER_RECIPES_WINDOWS )} " ,
363+ "docker_recipes_linux" : DOCKER_RECIPES_LINUX .name ,
364+ "docker_recipes_linux_size" : f"{ get_file_size_in_mb (DOCKER_RECIPES_LINUX ):.2f} MB" ,
365+ "docker_recipes_linux_hash" : f"{ get_sha256_from_file (DOCKER_RECIPES_LINUX )} " ,
366+ }
283367
284368
285369def copy_examples_to_output_dir (app : sphinx .application .Sphinx , exception : Exception ):
@@ -342,6 +426,7 @@ def render_examples_as_pdf(app: sphinx.application.Sphinx, exception: Exception)
342426
343427 Quarto needs to be installed in the system to render the PDF files. See
344428 https://quarto.org/docs/get-started/.
429+ Artifact
345430
346431 Parameters
347432 ----------
@@ -401,6 +486,7 @@ def setup(app: sphinx.application.Sphinx):
401486 # However, the examples are desired to be kept in the root directory. Once the
402487 # build has completed, no matter its success, the examples are removed from
403488 # the source directory.
489+ app .connect ("builder-inited" , copy_docker_files_to_static_dir )
404490 if BUILD_EXAMPLES :
405491 app .connect ("builder-inited" , copy_examples_files_to_source_dir )
406492 app .connect ("build-finished" , remove_examples_from_source_dir )
0 commit comments