diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index b2584655..28f44af4 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -1,5 +1,9 @@ -from typing import Any -from typing import Dict +from typing import Callable +from typing import NamedTuple +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING from pre_commit.languages import conda from pre_commit.languages import docker @@ -15,58 +19,43 @@ from pre_commit.languages import rust from pre_commit.languages import script from pre_commit.languages import swift from pre_commit.languages import system +from pre_commit.prefix import Prefix + +if TYPE_CHECKING: + from pre_commit.repository import Hook -# A language implements the following constant and functions in its module: -# -# # Use None for no environment -# ENVIRONMENT_DIR = 'foo_env' -# -# def get_default_version(): -# """Return a value to replace the 'default' value for language_version. -# -# return 'default' if there is no better option. -# """ -# -# def healthy(prefix, language_version): -# """Return whether or not the environment is considered functional.""" -# -# def install_environment(prefix, version, additional_dependencies): -# """Installs a repository in the given repository. Note that the current -# working directory will already be inside the repository. -# -# Args: -# prefix - `Prefix` bound to the repository. -# version - A version specified in the hook configuration or 'default'. -# """ -# -# def run_hook(hook, file_args, color): -# """Runs a hook and returns the returncode and output of running that -# hook. -# -# Args: -# hook - `Hook` -# file_args - The files to be run -# color - whether the hook should be given a pty (when supported) -# -# Returns: -# (returncode, output) -# """ +class Language(NamedTuple): + name: str + # Use `None` for no installation / environment + ENVIRONMENT_DIR: Optional[str] + # return a value to replace `'default` for `language_version` + get_default_version: Callable[[], str] + # return whether the environment is healthy (or should be rebuilt) + healthy: Callable[[Prefix, str], bool] + # install a repository for the given language and language_version + install_environment: Callable[[Prefix, str, Sequence[str]], None] + # execute a hook and return the exit code and output + run_hook: 'Callable[[Hook, Sequence[str], bool], Tuple[int, bytes]]' -languages: Dict[str, Any] = { - 'conda': conda, - 'docker': docker, - 'docker_image': docker_image, - 'fail': fail, - 'golang': golang, - 'node': node, - 'pygrep': pygrep, - 'python': python, - 'python_venv': python_venv, - 'ruby': ruby, - 'rust': rust, - 'script': script, - 'swift': swift, - 'system': system, + +# TODO: back to modules + Protocol: https://github.com/python/mypy/issues/5018 +languages = { + # BEGIN GENERATED (testing/gen-languages-all) + 'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, healthy=conda.healthy, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501 + 'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, healthy=docker.healthy, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501 + 'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, healthy=docker_image.healthy, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501 + 'fail': Language(name='fail', ENVIRONMENT_DIR=fail.ENVIRONMENT_DIR, get_default_version=fail.get_default_version, healthy=fail.healthy, install_environment=fail.install_environment, run_hook=fail.run_hook), # noqa: E501 + 'golang': Language(name='golang', ENVIRONMENT_DIR=golang.ENVIRONMENT_DIR, get_default_version=golang.get_default_version, healthy=golang.healthy, install_environment=golang.install_environment, run_hook=golang.run_hook), # noqa: E501 + 'node': Language(name='node', ENVIRONMENT_DIR=node.ENVIRONMENT_DIR, get_default_version=node.get_default_version, healthy=node.healthy, install_environment=node.install_environment, run_hook=node.run_hook), # noqa: E501 + 'pygrep': Language(name='pygrep', ENVIRONMENT_DIR=pygrep.ENVIRONMENT_DIR, get_default_version=pygrep.get_default_version, healthy=pygrep.healthy, install_environment=pygrep.install_environment, run_hook=pygrep.run_hook), # noqa: E501 + 'python': Language(name='python', ENVIRONMENT_DIR=python.ENVIRONMENT_DIR, get_default_version=python.get_default_version, healthy=python.healthy, install_environment=python.install_environment, run_hook=python.run_hook), # noqa: E501 + 'python_venv': Language(name='python_venv', ENVIRONMENT_DIR=python_venv.ENVIRONMENT_DIR, get_default_version=python_venv.get_default_version, healthy=python_venv.healthy, install_environment=python_venv.install_environment, run_hook=python_venv.run_hook), # noqa: E501 + 'ruby': Language(name='ruby', ENVIRONMENT_DIR=ruby.ENVIRONMENT_DIR, get_default_version=ruby.get_default_version, healthy=ruby.healthy, install_environment=ruby.install_environment, run_hook=ruby.run_hook), # noqa: E501 + 'rust': Language(name='rust', ENVIRONMENT_DIR=rust.ENVIRONMENT_DIR, get_default_version=rust.get_default_version, healthy=rust.healthy, install_environment=rust.install_environment, run_hook=rust.run_hook), # noqa: E501 + 'script': Language(name='script', ENVIRONMENT_DIR=script.ENVIRONMENT_DIR, get_default_version=script.get_default_version, healthy=script.healthy, install_environment=script.install_environment, run_hook=script.run_hook), # noqa: E501 + 'swift': Language(name='swift', ENVIRONMENT_DIR=swift.ENVIRONMENT_DIR, get_default_version=swift.get_default_version, healthy=swift.healthy, install_environment=swift.install_environment, run_hook=swift.run_hook), # noqa: E501 + 'system': Language(name='system', ENVIRONMENT_DIR=system.ENVIRONMENT_DIR, get_default_version=system.get_default_version, healthy=system.healthy, install_environment=system.install_environment, run_hook=system.run_hook), # noqa: E501 + # END GENERATED } all_languages = sorted(languages) diff --git a/pre_commit/repository.py b/pre_commit/repository.py index a88566d0..83ed7027 100644 --- a/pre_commit/repository.py +++ b/pre_commit/repository.py @@ -113,6 +113,7 @@ class Hook(NamedTuple): logger.info('This may take a few minutes...') lang = languages[self.language] + assert lang.ENVIRONMENT_DIR is not None venv = environment_dir(lang.ENVIRONMENT_DIR, self.language_version) # There's potentially incomplete cleanup from previous runs diff --git a/testing/gen-languages-all b/testing/gen-languages-all new file mode 100755 index 00000000..add6752d --- /dev/null +++ b/testing/gen-languages-all @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import sys + +LANGUAGES = [ + 'conda', 'docker', 'docker_image', 'fail', 'golang', 'node', 'pygrep', + 'python', 'python_venv', 'ruby', 'rust', 'script', 'swift', 'system', +] +FIELDS = [ + 'ENVIRONMENT_DIR', 'get_default_version', 'healthy', 'install_environment', + 'run_hook', +] + + +def main() -> int: + print(f' # BEGIN GENERATED ({sys.argv[0]})') + for lang in LANGUAGES: + parts = [f' {lang!r}: Language(name={lang!r}'] + for k in FIELDS: + parts.append(f', {k}={lang}.{k}') + parts.append('), # noqa: E501') + print(''.join(parts)) + print(' # END GENERATED') + return 0 + + +if __name__ == '__main__': + exit(main()) diff --git a/tests/languages/all_test.py b/tests/languages/all_test.py deleted file mode 100644 index 2c3db7ca..00000000 --- a/tests/languages/all_test.py +++ /dev/null @@ -1,62 +0,0 @@ -import inspect -from typing import Sequence -from typing import Tuple - -import pytest - -from pre_commit.languages.all import all_languages -from pre_commit.languages.all import languages -from pre_commit.prefix import Prefix - - -def _argspec(annotations): - args = [k for k in annotations if k != 'return'] - return inspect.FullArgSpec( - args=args, annotations=annotations, - varargs=None, varkw=None, defaults=None, - kwonlyargs=[], kwonlydefaults=None, - ) - - -@pytest.mark.parametrize('language', all_languages) -def test_install_environment_argspec(language): - expected_argspec = _argspec({ - 'return': None, - 'prefix': Prefix, - 'version': str, - 'additional_dependencies': Sequence[str], - }) - argspec = inspect.getfullargspec(languages[language].install_environment) - assert argspec == expected_argspec - - -@pytest.mark.parametrize('language', all_languages) -def test_ENVIRONMENT_DIR(language): - assert hasattr(languages[language], 'ENVIRONMENT_DIR') - - -@pytest.mark.parametrize('language', all_languages) -def test_run_hook_argspec(language): - expected_argspec = _argspec({ - 'return': Tuple[int, bytes], - 'hook': 'Hook', 'file_args': Sequence[str], 'color': bool, - }) - argspec = inspect.getfullargspec(languages[language].run_hook) - assert argspec == expected_argspec - - -@pytest.mark.parametrize('language', all_languages) -def test_get_default_version_argspec(language): - expected_argspec = _argspec({'return': str}) - argspec = inspect.getfullargspec(languages[language].get_default_version) - assert argspec == expected_argspec - - -@pytest.mark.parametrize('language', all_languages) -def test_healthy_argspec(language): - expected_argspec = _argspec({ - 'return': bool, - 'prefix': Prefix, 'language_version': str, - }) - argspec = inspect.getfullargspec(languages[language].healthy) - assert argspec == expected_argspec