diff --git a/sphinx_gallery/downloads.py b/sphinx_gallery/downloads.py index 17dc50c99..1630df348 100644 --- a/sphinx_gallery/downloads.py +++ b/sphinx_gallery/downloads.py @@ -4,9 +4,8 @@ # License: 3-clause BSD import os -import zipfile -from .utils import _replace_md5 +from .utils import zip_files CODE_ZIP_DOWNLOAD = """ @@ -53,14 +52,7 @@ def python_zip(file_list, gallery_path, extension=".py"): elif extension == ".ipynb": zipname += "_jupyter" zipname = os.path.join(gallery_path, zipname + ".zip") - zipname_new = zipname + ".new" - with zipfile.ZipFile(zipname_new, mode="w") as zipf: - for fname in file_list: - if extension is not None: - fname = os.path.splitext(fname)[0] + extension - zipf.write(fname, os.path.relpath(fname, gallery_path)) - _replace_md5(zipname_new) - return zipname + return zip_files(file_list, zipname, gallery_path, extension) def list_downloadable_sources(target_dir, extensions=(".py",)): diff --git a/sphinx_gallery/gen_rst.py b/sphinx_gallery/gen_rst.py index 86dd5208a..3fc5488ba 100644 --- a/sphinx_gallery/gen_rst.py +++ b/sphinx_gallery/gen_rst.py @@ -46,6 +46,7 @@ from .utils import ( scale_image, get_md5sum, + zip_files, _replace_md5, optipng, status_iterator, @@ -234,6 +235,12 @@ def __exit__(self, type_, value, tb): :download:`Download Jupyter notebook: {0} <{0}>` """ +ZIP_DOWNLOAD = """ + .. container:: sphx-glr-download sphx-glr-download-zip + + :download:`Download both (zipped): {0} <{0}>` +""" + RECOMMENDATIONS_INCLUDE = """\n .. include:: {0}.recommendations """ @@ -1263,12 +1270,17 @@ def generate_file_rst(fname, target_dir, src_dir, gallery_conf, seen_backrefs=No ) save_thumbnail(image_path_template, src_file, script_vars, file_conf, gallery_conf) + files_to_zip = [target_file] if target_file.suffix in gallery_conf["notebook_extensions"]: example_nb = jupyter_notebook(script_blocks, gallery_conf, target_dir) ipy_fname = target_file.with_suffix(".ipynb.new") save_notebook(example_nb, ipy_fname) _replace_md5(ipy_fname, mode="t") + files_to_zip += [target_file.with_suffix(".ipynb")] + + # Produce the zip file of all sources + zip_files(files_to_zip, target_file.with_suffix(".zip"), target_dir) # Write names if gallery_conf["inspect_global_variables"]: @@ -1479,9 +1491,11 @@ def save_rst_example( example_rst += jupyterlite_rst if save_notebook: - example_rst += NOTEBOOK_DOWNLOAD.format(example_file.with_suffix(".ipynb").name) + ipynb_download_file = example_file.with_suffix(".ipynb").name + example_rst += NOTEBOOK_DOWNLOAD.format(ipynb_download_file) example_rst += CODE_DOWNLOAD.format(example_file.name, language) + example_rst += ZIP_DOWNLOAD.format(example_file.with_suffix(".zip").name) if gallery_conf["recommender"]["enable"]: # extract the filename without the extension diff --git a/sphinx_gallery/utils.py b/sphinx_gallery/utils.py index d96f483e5..f6e08af35 100644 --- a/sphinx_gallery/utils.py +++ b/sphinx_gallery/utils.py @@ -11,6 +11,7 @@ import re from shutil import move, copyfile import subprocess +import zipfile from sphinx.errors import ExtensionError import sphinx.util @@ -167,6 +168,24 @@ def _replace_md5(fname_new, fname_old=None, method="move", mode="b"): assert os.path.isfile(fname_old) +def zip_files(file_list, zipname, relative_to, extension=None): + """ + Creates a zip file with the given files. + + A zip file named `zipname` will be created containing the files listed in + `file_list`. The zip file contents will be stored with their paths stripped to be + relative to `relative_to`. + """ + zipname_new = str(zipname) + ".new" + with zipfile.ZipFile(zipname_new, mode="w") as zipf: + for fname in file_list: + if extension is not None: + fname = os.path.splitext(fname)[0] + extension + zipf.write(fname, os.path.relpath(fname, relative_to)) + _replace_md5(zipname_new) + return zipname + + def _has_pypandoc(): """Check if pypandoc package available.""" try: