diff --git a/CHANGELOGS.rst b/CHANGELOGS.rst index c19c718..3dcff97 100644 --- a/CHANGELOGS.rst +++ b/CHANGELOGS.rst @@ -4,6 +4,7 @@ Change Logs 0.3.0 +++++ +* :pr:`33`: add simple command line to convert images to pdf * :pr:`28`: add syntax to check the readme syntax * :pr:`27`: fix missing __text_signature__ in docassert diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b4e1709 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,15 @@ +# Code of Conduct + +We are a community based on openness, as well as friendly and didactic discussions. + +We aspire to treat everybody equally, and value their contributions. + +Decisions are made based on technical merit and consensus. + +Code is not the only way to help the project. Reviewing pull requests, +answering questions to help others on mailing lists or issues, organizing and +teaching tutorials, working on the website, improving the documentation, are +all priceless contributions. + +We abide by the principles of openness, respect, and consideration of others of +the Python Software Foundation: https://www.python.org/psf/codeofconduct/ diff --git a/_unittests/ut_tools/data/image.png b/_unittests/ut_tools/data/image.png new file mode 100644 index 0000000..5bd15e9 Binary files /dev/null and b/_unittests/ut_tools/data/image.png differ diff --git a/_unittests/ut_tools/data/img4.png b/_unittests/ut_tools/data/img4.png new file mode 100644 index 0000000..65f98e4 Binary files /dev/null and b/_unittests/ut_tools/data/img4.png differ diff --git a/_unittests/ut_tools/data/mazures0.jpg b/_unittests/ut_tools/data/mazures0.jpg new file mode 100644 index 0000000..f891ed7 Binary files /dev/null and b/_unittests/ut_tools/data/mazures0.jpg differ diff --git a/_unittests/ut_tools/data/mazures1.jpg b/_unittests/ut_tools/data/mazures1.jpg new file mode 100644 index 0000000..26326f4 Binary files /dev/null and b/_unittests/ut_tools/data/mazures1.jpg differ diff --git a/_unittests/ut_tools/test_imgexport.py b/_unittests/ut_tools/test_imgexport.py new file mode 100644 index 0000000..53dac2c --- /dev/null +++ b/_unittests/ut_tools/test_imgexport.py @@ -0,0 +1,27 @@ +import os +import unittest +from sphinx_runpython.ext_test_case import ExtTestCase +from sphinx_runpython.tools.img_export import images2pdf + + +class TestImgExport(ExtTestCase): + + def test_export1(self): + dest = "test_export1.pdf" + data = os.path.join(os.path.dirname(__file__), "data", "mazures1.jpg") + datap = os.path.join(os.path.dirname(__file__), "data", "*.jpg") + res = images2pdf([data, datap], dest) + self.assertExists(dest) + self.assertEqual(len(res), 3) + + def test_export2(self): + dest = "test_export2.pdf" + data = os.path.join(os.path.dirname(__file__), "data", "mazures1.jpg") + datap = os.path.join(os.path.dirname(__file__), "data", "*.jpg") + res = images2pdf(",".join([data, datap]), dest) + self.assertExists(dest) + self.assertEqual(len(res), 3) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/requirements-dev.txt b/requirements-dev.txt index 57821d1..0b4e68a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,6 +2,7 @@ black coverage flake8 furo +img2pdf isort matplotlib numpydoc diff --git a/sphinx_runpython/_cmd_helper.py b/sphinx_runpython/_cmd_helper.py index 0817d5c..402754e 100644 --- a/sphinx_runpython/_cmd_helper.py +++ b/sphinx_runpython/_cmd_helper.py @@ -2,7 +2,6 @@ import os from argparse import ArgumentParser from tempfile import TemporaryDirectory -from .convert import convert_ipynb_to_gallery def get_parser(): @@ -12,7 +11,8 @@ def get_parser(): epilog="", ) parser.add_argument( - "command", help="Command to run, only 'nb2py' or 'readme' are available" + "command", + help="Command to run, only 'nb2py', 'readme', 'img2pdf' are available", ) parser.add_argument( "-p", "--path", help="Folder or file which contains the files to process" @@ -23,11 +23,18 @@ def get_parser(): help="Recursive search.", action="store_true", ) + parser.add_argument( + "-o", + "--output", + help="output", + ) parser.add_argument("-v", "--verbose", help="verbosity", default=1, type=int) return parser def nb2py(infolder: str, recursive: bool = False, verbose: int = 0): + from .convert import convert_ipynb_to_gallery + if not os.path.exists(infolder): raise FileNotFoundError(f"Unable to find {infolder!r}.") patterns = [infolder + "/*.ipynb", infolder + "/**/*.ipynb"] @@ -47,6 +54,11 @@ def process_args(args): if cmd == "nb2py": nb2py(args.path, recursive=args.recursive, verbose=args.verbose) return + if cmd == "img2pdf": + from .tools.img_export import images2pdf + + images2pdf(args.path, args.output, verbose=args.verbose) + return if cmd == "readme": from .readme import check_readme_syntax diff --git a/sphinx_runpython/tools/__init__.py b/sphinx_runpython/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sphinx_runpython/tools/img_export.py b/sphinx_runpython/tools/img_export.py new file mode 100644 index 0000000..1521e6a --- /dev/null +++ b/sphinx_runpython/tools/img_export.py @@ -0,0 +1,71 @@ +import glob +import os +from typing import List, Optional, Sequence, Union + + +def images2pdf( + images: Union[str, Sequence[str]], output: Optional[str], verbose: int = 0 +) -> List[str]: + """ + Merges multiples images into one single pdf. + Relies on :epkg:`img2pdf`. If an image name contains + ``'*'``, the function assumes it is a pattern and + uses :mod:`glob`. + + :param images: images to merge, it can be a comma separated values or a folder + :param output: output filename or stream + :param verbose: verbosity + """ + from img2pdf import convert + + if isinstance(images, str): + if "," in images: + images = images.split(",") + elif "*" in images: + images = [images] + elif os.path.exists(images): + images = [images] + else: + raise RuntimeError(f"Unable to deal with images={images!r}") + elif not isinstance(images, (list, tuple)): + raise TypeError("Images must be a list.") # pragma: no cover + + all_images = [] + for img in images: + if "*" in img: + if verbose: + print(f"[images2pdf] look into {img!r}") + names = glob.glob(img) + if verbose: + print(f"[images2pdf] add {len(names)} images") + all_images.extend(names) + else: + if verbose: + print(f"[images2pdf] add {img!r}") + all_images.append(img) + + if verbose > 1: + for i, img in enumerate(all_images): + print(f"[images2pdf] {i + 1}/{len(all_images)} {img!r}") + + st, close = ( + (open(output, "wb"), True) if isinstance(output, str) else (output, False) + ) + + for img in all_images: + assert not isinstance(img, str) or os.path.exists( + img + ), f"Unable to find image {img!r}." + + try: + convert(all_images, outputstream=st, with_pdfrw=False) + except TypeError as e: + raise TypeError( + f"Unable to process container type {type(all_images)} " + f"and type {type(all_images[0])}." + ) from e + + if close: + st.close() + + return all_images