pax_global_header00006660000000000000000000000064141762241230014514gustar00rootroot0000000000000052 comment=b8b22e4d8dd62bd4c60d75c8c33c8975b8cfd4d3 ots-python-8.2.1/000077500000000000000000000000001417622412300136505ustar00rootroot00000000000000ots-python-8.2.1/.github/000077500000000000000000000000001417622412300152105ustar00rootroot00000000000000ots-python-8.2.1/.github/workflows/000077500000000000000000000000001417622412300172455ustar00rootroot00000000000000ots-python-8.2.1/.github/workflows/ci.yml000066400000000000000000000044311417622412300203650ustar00rootroot00000000000000name: Build + Deploy on: push: branches: [main] tags: ["v*.*.*"] pull_request: branches: [main] release: types: - published jobs: build_sdist: name: Build sdist runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 with: submodules: true - name: Set up Python uses: actions/setup-python@v2 with: python-version: "3.x" - name: Build sdist run: pipx run --spec build pyproject-build --sdist - name: Check sdist metadata run: pipx run twine check dist/*.tar.gz - uses: actions/upload-artifact@v2 with: path: dist/*.tar.gz build_wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ "ubuntu-latest", "macos-latest", "windows-2016" ] env: CIBW_ARCHS_LINUX: x86_64 CIBW_ARCHS_MACOS: x86_64 universal2 # Building for one python version is enough, since this is a # ctypes-based 'universal binary wheel' (py3-none-*) not # linked to a specific python version or implementation. CIBW_BUILD: "cp39-*" # Increase pip debugging output CIBW_BUILD_VERBOSITY: 1 # Increase delocate-wheel debugging output # CIBW_REPAIR_WHEEL_COMMAND_MACOS: delocate-listdeps {wheel} && delocate-wheel -vv --require-archs {delocate_archs} -w {dest_dir} {wheel} steps: - uses: actions/checkout@v2 with: submodules: recursive fetch-depth: 0 - name: Set up msbuild (Windows-only) uses: microsoft/setup-msbuild@v1.0.2 if: startsWith(matrix.os, 'windows') - uses: actions/setup-python@v2 with: python-version: 3.9 - name: Build wheels run: pipx run cibuildwheel --output-dir dist - uses: actions/upload-artifact@v2 with: path: dist/*.whl deploy: name: Upload if release needs: [build_sdist, build_wheels] runs-on: ubuntu-latest if: github.event_name == 'release' && github.event.action == 'published' steps: - uses: actions/download-artifact@v2 with: name: artifact path: dist - uses: pypa/gh-action-pypi-publish@v1.4.2 with: user: __token__ password: ${{ secrets.PYPI_PASSWORD }} ots-python-8.2.1/.gitignore000066400000000000000000000025151417622412300156430ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # autogenerated vesion file src/python/ots/_version.py # executable built in-place src/python/ots/ots-sanitize # uncompressed source tarball src/c/ots ots-python-8.2.1/.gitmodules000066400000000000000000000000001417622412300160130ustar00rootroot00000000000000ots-python-8.2.1/LICENSE000066400000000000000000000027151417622412300146620ustar00rootroot00000000000000Copyright (c) 2009-2017 The OTS Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ots-python-8.2.1/MANIFEST.in000066400000000000000000000002441417622412300154060ustar00rootroot00000000000000recursive-include src/c/ots * prune src/c/ots/tests/fonts prune src/c/ots/third_party/brotli/tests/testdata prune src/c/ots/third_party/woff2/brotli/tests/testdata ots-python-8.2.1/README.md000066400000000000000000000003031417622412300151230ustar00rootroot00000000000000![CI Status](https://github.com/googlefonts/ots-python/actions/workflows/ci.yml/badge.svg?branch=main) # ots-python Python wheels for the [OpenType Sanitizer](https://github.com/khaledhosny/ots) ots-python-8.2.1/build.py000077500000000000000000000116411417622412300153270ustar00rootroot00000000000000#!/usr/bin/env python3 """Run meson and ninja to build the ots-sanitize executable from source. NOTE: This script requires Python 3.6 or above. However the generated binary is independent from the python version used to run it. """ import sys import platform import enum from pathlib import Path import os import subprocess import shutil import errno import argparse class MacTarget(enum.Enum): X86_64 = "x86_64" ARM64 = "arm64" UNIVERSAL2 = "universal2" PLATFORM = platform.uname() ROOT = Path(__file__).parent.resolve() SRC_DIR = ROOT.joinpath("src", "c", "ots") DEFAULT_BUILD_DIR = ROOT / "build" / "meson" CROSS_FILES_DIR = ROOT / "cross-files" MACOS_CROSS_FILES = { MacTarget.ARM64: CROSS_FILES_DIR / "darwin" / "arm64", MacTarget.X86_64: CROSS_FILES_DIR / "darwin" / "x86_64", } TOOLS = { "meson": os.environ.get("MESON_EXE", "meson"), "ninja": os.environ.get("NINJA_EXE", "ninja"), } MESON_OPTIONS = [ "--backend=ninja", "--buildtype=release", "--strip", "-Ddebug=true", ] if PLATFORM.system == "Windows": MESON_OPTIONS.append("--force-fallback-for=zlib") if PLATFORM.system == "Darwin": MESON_OPTIONS.append("--force-fallback-for=google-brotli,lz4") native_machine = MacTarget(PLATFORM.machine) cross_machine = ( MacTarget.ARM64 if native_machine == MacTarget.X86_64 else MacTarget.X86_64 ) MACOS_CROSS_FILES = { native_machine: [""], cross_machine: [MACOS_CROSS_FILES[cross_machine]], } MACOS_CROSS_FILES[MacTarget.UNIVERSAL2] = ( MACOS_CROSS_FILES[MacTarget.X86_64] + MACOS_CROSS_FILES[MacTarget.ARM64] ) class ExecutableNotFound(FileNotFoundError): def __init__(self, name, path): msg = f"{name} executable not found: '{path}'" super().__init__(errno.ENOENT, msg) def check_tools(): for name, path in TOOLS.items(): if shutil.which(path) is None: raise ExecutableNotFound(name, path) def configure(build_dir, reconfigure=False, cross_file=""): meson_cmd = [TOOLS["meson"]] + MESON_OPTIONS + [str(build_dir), str(SRC_DIR)] if cross_file: meson_cmd.insert(1, f"--cross-file={cross_file}") if not (build_dir / "build.ninja").exists(): subprocess.run(meson_cmd, check=True, env=os.environ) elif reconfigure: subprocess.run(meson_cmd + ["--reconfigure"], check=True, env=os.environ) def make(build_dir, *targets, clean=False): ninja_cmd = [TOOLS["ninja"], "-C", str(build_dir)] targets = list(targets) if clean: subprocess.run( ninja_cmd + ["-t", "clean"] + targets, check=True, env=os.environ ) subprocess.run(ninja_cmd + targets, check=True, env=os.environ) def main(args=None): parser = argparse.ArgumentParser() parser.add_argument("-f", "--force", action="store_true") parser.add_argument("--build-dir", default=DEFAULT_BUILD_DIR, type=Path) parser.add_argument( "--mac-target", default=None, metavar="ARCH", type=MacTarget, help=(f"{(', ').join(v.value for v in MacTarget)}"), ) parser.add_argument("targets", nargs="*") options = parser.parse_args(args) check_tools() build_dirs = [options.build_dir] cross_files = [""] if options.mac_target is not None: if PLATFORM.system == "Darwin": cross_files = MACOS_CROSS_FILES[options.mac_target] if options.mac_target == MacTarget.UNIVERSAL2: build_dirs = [options.build_dir / arch for arch in ("x86_64", "arm64")] else: print( "WARNING: --mac-target option is ignored on non-mac platforms", file=sys.stderr, ) for cross_file, build_dir in zip(cross_files, build_dirs): try: configure( build_dir, reconfigure=options.force, cross_file=cross_file, ) make(build_dir, *options.targets, clean=options.force) except subprocess.CalledProcessError as e: return e.returncode if options.mac_target == MacTarget.UNIVERSAL2: # create universal binary by merging multiple archs with the 'lipo' tool: # https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary for filename in options.targets: arch_paths = [ options.build_dir / arch / filename for arch in ("x86_64", "arm64") ] dest_path = options.build_dir / filename subprocess.run( ["lipo", "-create", "-output", dest_path] + arch_paths, check=True, env=os.environ, ) # confirm that we got a 'fat' binary result = subprocess.run( ["lipo", "-archs", dest_path], check=True, capture_output=True ) assert "x86_64 arm64" == result.stdout.decode().strip() if __name__ == "__main__": sys.exit(main()) ots-python-8.2.1/config.sh000066400000000000000000000030321417622412300154470ustar00rootroot00000000000000#!/bin/bash # Define custom utilities # Test for OSX with [ -n "$IS_OSX" ] function pre_build { # Any stuff that you need to do before you start building the wheels # Runs in the root directory of this repository. # Travis only clones the latest 50 commits. We need the full repository # to compute the version string from the git metadata: # https://github.com/travis-ci/travis-ci/issues/3412#issuecomment-83993903 # https://github.com/pypa/setuptools_scm/issues/93 git fetch --unshallow } function run_tests { # The function is called from an empty temporary directory. cd .. # Get absolute path to the pre-compiled wheel wheelhouse=$(abspath wheelhouse) wheel=$(ls ${wheelhouse}/opentype_sanitizer*.whl | head -n 1) if [ ! -e "${wheel}" ]; then echo "error: can't find wheel in ${wheelhouse} folder" 1>&2 exit 1 fi # Install pre-compiled wheel and run tests against it tox -e py --installpkg "${wheel}" # clean up after us, or else running tox later on outside the docker # container can lead to permission errors rm -rf .tox } # override default 'install_delocate' as a temporary workaround for # embedded executable losing execute permissions on macOS # https://github.com/matthew-brett/delocate/issues/42 # https://github.com/matthew-brett/delocate/pull/43 # TODO: remove this once new delocate with the above patch is released function install_delocate { check_pip $PIP_CMD install git+https://github.com/matthew-brett/delocate.git#egg=delocate } ots-python-8.2.1/cross-files/000077500000000000000000000000001417622412300161015ustar00rootroot00000000000000ots-python-8.2.1/cross-files/darwin/000077500000000000000000000000001417622412300173655ustar00rootroot00000000000000ots-python-8.2.1/cross-files/darwin/arm64000066400000000000000000000005121417622412300202370ustar00rootroot00000000000000[host_machine] system = 'darwin' cpu_family = 'aarch64' cpu = 'arm64' endian = 'little' [constants] common_flags = ['--target=arm64-apple-macos11'] [built-in options] c_args = common_flags c_link_args = common_flags cpp_args = common_flags cpp_link_args = common_flags [binaries] c = 'cc' cpp = 'c++' ar = 'ar' strip = 'strip' ots-python-8.2.1/cross-files/darwin/x86_64000066400000000000000000000005151417622412300202470ustar00rootroot00000000000000[host_machine] system = 'darwin' cpu_family = 'x86_64' cpu = 'x86_64' endian = 'little' [constants] common_flags = ['--target=x86_64-apple-macos10.9'] [built-in options] c_args = common_flags c_link_args = common_flags cpp_args = common_flags cpp_link_args = common_flags [binaries] c = 'cc' cpp = 'c++' ar = 'ar' strip = 'strip' ots-python-8.2.1/pyproject.toml000066400000000000000000000002351417622412300165640ustar00rootroot00000000000000[build-system] requires = [ "setuptools", "wheel", "setuptools_scm", "meson >= 0.48", "ninja", ] build-backend = "setuptools.build_meta" ots-python-8.2.1/setup.cfg000066400000000000000000000004671417622412300155000ustar00rootroot00000000000000[download] ; OpenType Sanitizer version that is downloaded from setup.py version = 8.2.1 ; expected SHA-256 of the downloaded tarball. E.g. you can calculate it with: ; $ shasum -a 256 ots-X.X.X.tar.xz sha256 = c9948c8ae61c50937afc6a0da6fb1c351fb7ea1ab30f796679d165447667e89f [metadata] license_file = LICENSE ots-python-8.2.1/setup.py000077500000000000000000000222331417622412300153670ustar00rootroot00000000000000from __future__ import print_function, absolute_import from setuptools import setup, find_packages, Extension, Command from setuptools.command.build_ext import build_ext from setuptools.command.egg_info import egg_info from distutils.file_util import copy_file from distutils.dir_util import mkpath, remove_tree from distutils.util import get_platform from distutils import log import os import sys import subprocess if sys.version_info[:2] < (3, 6): sys.exit( "error: Python 3.6 is required to run setup.py. \n" "The generated wheel will be compatible with both py27 and py3+" ) cmdclass = {} try: from wheel.bdist_wheel import bdist_wheel except ImportError: pass else: class UniversalBdistWheel(bdist_wheel): def get_tag(self): return ("py2.py3", "none") + bdist_wheel.get_tag(self)[2:] cmdclass["bdist_wheel"] = UniversalBdistWheel class Download(Command): user_options = [ ("version=", None, "ots source version number to download"), ("sha256=", None, "expected SHA-256 hash of the source archive"), ("download-dir=", "d", "where to unpack the 'ots' dir (default: src/c)"), ("clean", None, "remove existing directory before downloading"), ] boolean_options = ["clean"] URL_TEMPLATE = ( "https://github.com/khaledhosny/ots/releases/download/" "v{version}/ots-{version}.tar.xz" ) def initialize_options(self): self.version = None self.download_dir = None self.clean = False self.sha256 = None def finalize_options(self): if self.version is None: from distutils.errors import DistutilsSetupError raise DistutilsSetupError("must specify --version to download") if self.sha256 is None: from distutils.errors import DistutilsSetupError raise DistutilsSetupError("must specify --sha256 of downloaded file") if self.download_dir is None: self.download_dir = os.path.join("src", "c") self.url = self.URL_TEMPLATE.format(**vars(self)) def run(self): from urllib.request import urlopen from io import BytesIO import tarfile import lzma import hashlib output_dir = os.path.join(self.download_dir, "ots") if self.clean and os.path.isdir(output_dir): remove_tree(output_dir, verbose=self.verbose, dry_run=self.dry_run) if os.path.isdir(output_dir): log.info("{} was already downloaded".format(output_dir)) else: archive_name = self.url.rsplit("/", 1)[-1] mkpath(self.download_dir, verbose=self.verbose, dry_run=self.dry_run) log.info("downloading {}".format(self.url)) if not self.dry_run: # response is not seekable so we first download *.tar.xz to an # in-memory file, and then extract all files to the output_dir # TODO: use hashlib to verify the SHA-256 hash f = BytesIO() with urlopen(self.url) as response: f.write(response.read()) f.seek(0) actual_sha256 = hashlib.sha256(f.getvalue()).hexdigest() if actual_sha256 != self.sha256: from distutils.errors import DistutilsSetupError raise DistutilsSetupError( "invalid SHA-256 checksum:\n" "actual: {}\n" "expected: {}".format(actual_sha256, self.sha256) ) log.info("unarchiving {} to {}".format(archive_name, output_dir)) if not self.dry_run: with lzma.open(f) as xz: with tarfile.open(fileobj=xz) as tar: filelist = tar.getmembers() first = filelist[0] if not (first.isdir() and first.name.startswith("ots")): from distutils.errors import DistutilsSetupError raise DistutilsSetupError( "The downloaded archive is not recognized as " "a valid ots source tarball" ) # strip the root 'ots-X.X.X' directory before extracting rootdir = first.name + "/" to_extract = [] for member in filelist[1:]: if member.name.startswith(rootdir): member.name = member.name[len(rootdir) :] to_extract.append(member) tar.extractall(output_dir, members=to_extract) class Executable(Extension): if os.name == "nt": suffix = ".exe" else: suffix = "" def __init__(self, name, script, options=None, output_dir=".", cwd=None, env=None): Extension.__init__(self, name, sources=[]) self.target = self.name.split(".")[-1] + self.suffix self.script = script self.options = options or [] self.output_dir = output_dir self.cwd = cwd self.env = env class ExecutableBuildExt(build_ext): def finalize_options(self): from distutils.ccompiler import get_default_compiler build_ext.finalize_options(self) if self.compiler is None: self.compiler = get_default_compiler(os.name) self._compiler_env = dict(os.environ) def get_ext_filename(self, ext_name): for ext in self.extensions: if isinstance(ext, Executable): return os.path.join(*ext_name.split(".")) + ext.suffix return build_ext.get_ext_filename(self, ext_name) def run(self): self.run_command("download") if self.compiler == "msvc": self.call_vcvarsall_bat() build_ext.run(self) def call_vcvarsall_bat(self): import struct from distutils._msvccompiler import _get_vc_env arch = "x64" if struct.calcsize("P") * 8 == 64 else "x86" vc_env = _get_vc_env(arch) self._compiler_env.update(vc_env) def build_extension(self, ext): if not isinstance(ext, Executable): build_ext.build_extension(self, ext) return cmd = [sys.executable, ext.script] + ext.options + [ext.target] if self.force: cmd += ["--force"] log.debug("running '{}'".format(" ".join(cmd))) if not self.dry_run: env = self._compiler_env.copy() if ext.env: env.update(ext.env) p = subprocess.run(cmd, cwd=ext.cwd, env=env) if p.returncode != 0: from distutils.errors import DistutilsExecError raise DistutilsExecError( "running '{}' script failed".format(ext.script) ) exe_fullpath = os.path.join(ext.output_dir, ext.target) dest_path = self.get_ext_fullpath(ext.name) mkpath(os.path.dirname(dest_path), verbose=self.verbose, dry_run=self.dry_run) copy_file(exe_fullpath, dest_path, verbose=self.verbose, dry_run=self.dry_run) class CustomEggInfo(egg_info): def run(self): # make sure the ots source is downloaded before creating sdist manifest self.run_command("download") egg_info.run(self) cmdclass["download"] = Download cmdclass["build_ext"] = ExecutableBuildExt cmdclass["egg_info"] = CustomEggInfo build_options = [] platform_tags = get_platform().split("-") if "macosx" in platform_tags: if "universal2" in platform_tags: build_options.append("--mac-target=universal2") elif "arm64" in platform_tags: build_options.append("--mac-target=arm64") ots_sanitize = Executable( "ots.ots-sanitize", script="build.py", options=build_options, output_dir=os.path.join("build", "meson"), ) with open("README.md", "r", encoding="utf-8") as readme: long_description = readme.read() setup( name="opentype-sanitizer", use_scm_version={"write_to": "src/python/ots/_version.py"}, description=("Python wrapper for the OpenType Sanitizer"), long_description=long_description, long_description_content_type="text/markdown", author="Cosimo Lupo", author_email="cosimo@anthrotype.com", url="https://github.com/googlefonts/ots-python", license="OpenSource, BSD-style", platforms=["posix", "nt"], package_dir={"": "src/python"}, packages=find_packages("src/python"), ext_modules=[ots_sanitize], zip_safe=False, cmdclass=cmdclass, setup_requires=["setuptools_scm"], extras_require={"testing": ["pytest"]}, classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: Other Environment", "Intended Audience :: Developers", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Topic :: Text Processing :: Fonts", "Topic :: Multimedia :: Graphics", ], ) ots-python-8.2.1/src/000077500000000000000000000000001417622412300144375ustar00rootroot00000000000000ots-python-8.2.1/src/python/000077500000000000000000000000001417622412300157605ustar00rootroot00000000000000ots-python-8.2.1/src/python/ots/000077500000000000000000000000001417622412300165655ustar00rootroot00000000000000ots-python-8.2.1/src/python/ots/__init__.py000066400000000000000000000052371417622412300207050ustar00rootroot00000000000000from __future__ import absolute_import import subprocess import sys import os OTS_SANITIZE = os.path.join(os.path.dirname(__file__), "ots-sanitize") __all__ = ["sanitize", "OTSError", "CalledProcessError"] try: from ._version import version as __version__ except ImportError: __version__ = "0.0.0+unknown" class OTSError(Exception): pass # subprocess.CalledProcessError on python < 3.5 doesn't have 'stderr' argument. # Regardless, it's a good idea to wrap subprocess' exceptions with our own. class CalledProcessError(OTSError, subprocess.CalledProcessError): def __init__(self, returncode, cmd, output=None, stderr=None): subprocess.CalledProcessError.__init__(self, returncode, cmd, output=output) self.stderr = stderr @property def stdout(self): """Alias for output attribute, to match stderr""" return self.output try: from subprocess import CompletedProcess as _CompletedProcess class CompletedProcess(_CompletedProcess): pass except ImportError: # only added from python 3.5 from collections import namedtuple class CompletedProcess( namedtuple("CompletedProcess", "args returncode stdout stderr") ): def check_returncode(self): if self.returncode: raise CalledProcessError( self.returncode, self.args, self.stdout, self.stderr ) def _run(args, capture_output=False, check=False, **kwargs): if capture_output: if ("stdout" in kwargs) or ("stderr" in kwargs): raise ValueError( "stdout and stderr arguments may not be used with capture_output." ) kwargs["stdout"] = subprocess.PIPE kwargs["stderr"] = subprocess.PIPE process = subprocess.Popen(args, **kwargs) try: stdout, stderr = process.communicate() except: process.kill() process.wait() raise retcode = process.poll() if check and retcode: raise CalledProcessError(retcode, args, output=stdout, stderr=stderr) return CompletedProcess(args, retcode, stdout, stderr) def sanitize(*args, **kwargs): """Run the embedded ots-sanitize executable with the list of positional arguments (strings). Return an ots.CompletedProcess object with the following attributes: args, returncode, stdout, stderr. If check=True, and the subprocess exits with a non-zero exit code, an ots.CalledProcessError exception will be raised. If capture_output=True, stdout and stderr will be captured. All extra keyword arguments are forwarded to subprocess.Popen constructor. """ return _run([OTS_SANITIZE] + list(args), **kwargs) ots-python-8.2.1/src/python/ots/__main__.py000066400000000000000000000003411417622412300206550ustar00rootroot00000000000000from __future__ import absolute_import import sys import ots def main(args=None): if args is None: args = sys.argv[1:] return ots.sanitize(*args).returncode if __name__ == "__main__": sys.exit(main()) ots-python-8.2.1/tests/000077500000000000000000000000001417622412300150125ustar00rootroot00000000000000ots-python-8.2.1/tests/test_ots.py000066400000000000000000000011371417622412300172320ustar00rootroot00000000000000from __future__ import absolute_import, unicode_literals import pytest import subprocess import ots def test_sanitize(): p = ots.sanitize() assert p.returncode == 1 assert len(p.args) == 1 assert p.args[0].endswith("ots-sanitize") assert p.stdout == None assert p.stderr == None def test_check_error(): with pytest.raises(subprocess.CalledProcessError): ots.sanitize("--foo", "bar", check=True) def test_capture_output(): p = ots.sanitize(capture_output=True) assert len(p.stdout) == 0 stderr = p.stderr.decode() assert stderr.startswith("Usage: ") ots-python-8.2.1/tox.ini000066400000000000000000000017361417622412300151720ustar00rootroot00000000000000[tox] dist_name = opentype_sanitizer envlist = py37 minversion = 3.4.0 isolated_build = true [testenv] description = run the tests with pytest under {basepython} download = true extras = testing commands = pytest {posargs} [testenv:py27] description = run the tests with pytest under python2.7 (build wheel first) basepython = {env:TOX_PYTHON_27:python2.7} deps = pytest skip_install = true changedir = {toxinidir} commands = pip install --no-index --find-links dist --only-binary {[tox]dist_name} {[tox]dist_name} pytest {posargs} [testenv:wheel] description = build py2.py3 universal binary wheel deps = pip setuptools setuptools-scm wheel meson==0.55.2 ninja skip_install = true commands = pip wheel --no-build-isolation --use-deprecated legacy-resolver \ -w dist --no-deps --pre --find-links {distdir} \ --no-cache-dir --no-binary {[tox]dist_name} {[tox]dist_name} [pytest] testpaths = tests addopts = -v -r a