pre-commit-ex 4.5.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. pre_commit/__init__.py +0 -0
  2. pre_commit/__main__.py +7 -0
  3. pre_commit/all_languages.py +50 -0
  4. pre_commit/clientlib.py +551 -0
  5. pre_commit/color.py +109 -0
  6. pre_commit/commands/__init__.py +0 -0
  7. pre_commit/commands/autoupdate.py +215 -0
  8. pre_commit/commands/clean.py +16 -0
  9. pre_commit/commands/gc.py +98 -0
  10. pre_commit/commands/hazmat.py +95 -0
  11. pre_commit/commands/hook_impl.py +272 -0
  12. pre_commit/commands/init_templatedir.py +39 -0
  13. pre_commit/commands/install_uninstall.py +167 -0
  14. pre_commit/commands/migrate_config.py +135 -0
  15. pre_commit/commands/run.py +474 -0
  16. pre_commit/commands/sample_config.py +18 -0
  17. pre_commit/commands/try_repo.py +77 -0
  18. pre_commit/commands/validate_config.py +18 -0
  19. pre_commit/commands/validate_manifest.py +18 -0
  20. pre_commit/constants.py +13 -0
  21. pre_commit/envcontext.py +62 -0
  22. pre_commit/error_handler.py +81 -0
  23. pre_commit/errors.py +5 -0
  24. pre_commit/file_lock.py +75 -0
  25. pre_commit/git.py +245 -0
  26. pre_commit/hook.py +60 -0
  27. pre_commit/lang_base.py +196 -0
  28. pre_commit/languages/__init__.py +0 -0
  29. pre_commit/languages/conda.py +77 -0
  30. pre_commit/languages/coursier.py +76 -0
  31. pre_commit/languages/dart.py +97 -0
  32. pre_commit/languages/docker.py +181 -0
  33. pre_commit/languages/docker_image.py +32 -0
  34. pre_commit/languages/dotnet.py +111 -0
  35. pre_commit/languages/fail.py +27 -0
  36. pre_commit/languages/golang.py +161 -0
  37. pre_commit/languages/haskell.py +56 -0
  38. pre_commit/languages/julia.py +133 -0
  39. pre_commit/languages/lua.py +75 -0
  40. pre_commit/languages/node.py +110 -0
  41. pre_commit/languages/perl.py +50 -0
  42. pre_commit/languages/pygrep.py +133 -0
  43. pre_commit/languages/python.py +228 -0
  44. pre_commit/languages/r.py +278 -0
  45. pre_commit/languages/ruby.py +145 -0
  46. pre_commit/languages/rust.py +160 -0
  47. pre_commit/languages/swift.py +50 -0
  48. pre_commit/languages/unsupported.py +10 -0
  49. pre_commit/languages/unsupported_script.py +32 -0
  50. pre_commit/logging_handler.py +42 -0
  51. pre_commit/main.py +472 -0
  52. pre_commit/meta_hooks/__init__.py +0 -0
  53. pre_commit/meta_hooks/check_hooks_apply.py +43 -0
  54. pre_commit/meta_hooks/check_useless_excludes.py +83 -0
  55. pre_commit/meta_hooks/identity.py +17 -0
  56. pre_commit/output.py +33 -0
  57. pre_commit/parse_shebang.py +85 -0
  58. pre_commit/prefix.py +18 -0
  59. pre_commit/repository.py +237 -0
  60. pre_commit/resources/__init__.py +0 -0
  61. pre_commit/resources/empty_template_.npmignore +1 -0
  62. pre_commit/resources/empty_template_Cargo.toml +7 -0
  63. pre_commit/resources/empty_template_LICENSE.renv +7 -0
  64. pre_commit/resources/empty_template_Makefile.PL +6 -0
  65. pre_commit/resources/empty_template_activate.R +440 -0
  66. pre_commit/resources/empty_template_environment.yml +9 -0
  67. pre_commit/resources/empty_template_go.mod +1 -0
  68. pre_commit/resources/empty_template_main.go +3 -0
  69. pre_commit/resources/empty_template_main.rs +1 -0
  70. pre_commit/resources/empty_template_package.json +4 -0
  71. pre_commit/resources/empty_template_pre-commit-package-dev-1.rockspec +12 -0
  72. pre_commit/resources/empty_template_pre_commit_placeholder_package.gemspec +6 -0
  73. pre_commit/resources/empty_template_pubspec.yaml +4 -0
  74. pre_commit/resources/empty_template_renv.lock +20 -0
  75. pre_commit/resources/empty_template_setup.py +4 -0
  76. pre_commit/resources/hook-tmpl +20 -0
  77. pre_commit/resources/rbenv.tar.gz +0 -0
  78. pre_commit/resources/ruby-build.tar.gz +0 -0
  79. pre_commit/resources/ruby-download.tar.gz +0 -0
  80. pre_commit/staged_files_only.py +113 -0
  81. pre_commit/store.py +235 -0
  82. pre_commit/util.py +239 -0
  83. pre_commit/xargs.py +184 -0
  84. pre_commit/yaml.py +19 -0
  85. pre_commit/yaml_rewrite.py +52 -0
  86. pre_commit_ex-4.5.1.dist-info/METADATA +63 -0
  87. pre_commit_ex-4.5.1.dist-info/RECORD +91 -0
  88. pre_commit_ex-4.5.1.dist-info/WHEEL +6 -0
  89. pre_commit_ex-4.5.1.dist-info/entry_points.txt +2 -0
  90. pre_commit_ex-4.5.1.dist-info/licenses/LICENSE +19 -0
  91. pre_commit_ex-4.5.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import os
5
+ import sys
6
+ from collections.abc import Generator
7
+ from collections.abc import Sequence
8
+
9
+ from pre_commit import lang_base
10
+ from pre_commit.envcontext import envcontext
11
+ from pre_commit.envcontext import PatchesT
12
+ from pre_commit.envcontext import SubstitutionT
13
+ from pre_commit.envcontext import UNSET
14
+ from pre_commit.envcontext import Var
15
+ from pre_commit.prefix import Prefix
16
+ from pre_commit.util import cmd_output_b
17
+
18
+ ENVIRONMENT_DIR = 'conda'
19
+ get_default_version = lang_base.basic_get_default_version
20
+ health_check = lang_base.basic_health_check
21
+ run_hook = lang_base.basic_run_hook
22
+
23
+
24
+ def get_env_patch(env: str) -> PatchesT:
25
+ # On non-windows systems executable live in $CONDA_PREFIX/bin, on Windows
26
+ # they can be in $CONDA_PREFIX/bin, $CONDA_PREFIX/Library/bin,
27
+ # $CONDA_PREFIX/Scripts and $CONDA_PREFIX. Whereas the latter only
28
+ # seems to be used for python.exe.
29
+ path: SubstitutionT = (os.path.join(env, 'bin'), os.pathsep, Var('PATH'))
30
+ if sys.platform == 'win32': # pragma: win32 cover
31
+ path = (env, os.pathsep, *path)
32
+ path = (os.path.join(env, 'Scripts'), os.pathsep, *path)
33
+ path = (os.path.join(env, 'Library', 'bin'), os.pathsep, *path)
34
+
35
+ return (
36
+ ('PYTHONHOME', UNSET),
37
+ ('VIRTUAL_ENV', UNSET),
38
+ ('CONDA_PREFIX', env),
39
+ ('PATH', path),
40
+ )
41
+
42
+
43
+ @contextlib.contextmanager
44
+ def in_env(prefix: Prefix, version: str) -> Generator[None]:
45
+ envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
46
+ with envcontext(get_env_patch(envdir)):
47
+ yield
48
+
49
+
50
+ def _conda_exe() -> str:
51
+ if os.environ.get('PRE_COMMIT_USE_MICROMAMBA'):
52
+ return 'micromamba'
53
+ elif os.environ.get('PRE_COMMIT_USE_MAMBA'):
54
+ return 'mamba'
55
+ else:
56
+ return 'conda'
57
+
58
+
59
+ def install_environment(
60
+ prefix: Prefix,
61
+ version: str,
62
+ additional_dependencies: Sequence[str],
63
+ ) -> None:
64
+ lang_base.assert_version_default('conda', version)
65
+
66
+ conda_exe = _conda_exe()
67
+
68
+ env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
69
+ cmd_output_b(
70
+ conda_exe, 'env', 'create', '-p', env_dir, '--file',
71
+ 'environment.yml', cwd=prefix.prefix_dir,
72
+ )
73
+ if additional_dependencies:
74
+ cmd_output_b(
75
+ conda_exe, 'install', '-p', env_dir, *additional_dependencies,
76
+ cwd=prefix.prefix_dir,
77
+ )
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import os.path
5
+ from collections.abc import Generator
6
+ from collections.abc import Sequence
7
+
8
+ from pre_commit import lang_base
9
+ from pre_commit.envcontext import envcontext
10
+ from pre_commit.envcontext import PatchesT
11
+ from pre_commit.envcontext import Var
12
+ from pre_commit.errors import FatalError
13
+ from pre_commit.parse_shebang import find_executable
14
+ from pre_commit.prefix import Prefix
15
+
16
+ ENVIRONMENT_DIR = 'coursier'
17
+
18
+ get_default_version = lang_base.basic_get_default_version
19
+ health_check = lang_base.basic_health_check
20
+ run_hook = lang_base.basic_run_hook
21
+
22
+
23
+ def install_environment(
24
+ prefix: Prefix,
25
+ version: str,
26
+ additional_dependencies: Sequence[str],
27
+ ) -> None:
28
+ lang_base.assert_version_default('coursier', version)
29
+
30
+ # Support both possible executable names (either "cs" or "coursier")
31
+ cs = find_executable('cs') or find_executable('coursier')
32
+ if cs is None:
33
+ raise AssertionError(
34
+ 'pre-commit requires system-installed "cs" or "coursier" '
35
+ 'executables in the application search path',
36
+ )
37
+
38
+ envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
39
+
40
+ def _install(*opts: str) -> None:
41
+ assert cs is not None
42
+ lang_base.setup_cmd(prefix, (cs, 'fetch', *opts))
43
+ lang_base.setup_cmd(prefix, (cs, 'install', '--dir', envdir, *opts))
44
+
45
+ with in_env(prefix, version):
46
+ channel = prefix.path('.pre-commit-channel')
47
+ if os.path.isdir(channel):
48
+ for app_descriptor in os.listdir(channel):
49
+ _, app_file = os.path.split(app_descriptor)
50
+ app, _ = os.path.splitext(app_file)
51
+ _install(
52
+ '--default-channels=false',
53
+ '--channel', channel,
54
+ app,
55
+ )
56
+ elif not additional_dependencies:
57
+ raise FatalError(
58
+ 'expected .pre-commit-channel dir or additional_dependencies',
59
+ )
60
+
61
+ if additional_dependencies:
62
+ _install(*additional_dependencies)
63
+
64
+
65
+ def get_env_patch(target_dir: str) -> PatchesT:
66
+ return (
67
+ ('PATH', (target_dir, os.pathsep, Var('PATH'))),
68
+ ('COURSIER_CACHE', os.path.join(target_dir, '.cs-cache')),
69
+ )
70
+
71
+
72
+ @contextlib.contextmanager
73
+ def in_env(prefix: Prefix, version: str) -> Generator[None]:
74
+ envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
75
+ with envcontext(get_env_patch(envdir)):
76
+ yield
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import os.path
5
+ import shutil
6
+ import tempfile
7
+ from collections.abc import Generator
8
+ from collections.abc import Sequence
9
+
10
+ from pre_commit import lang_base
11
+ from pre_commit.envcontext import envcontext
12
+ from pre_commit.envcontext import PatchesT
13
+ from pre_commit.envcontext import Var
14
+ from pre_commit.prefix import Prefix
15
+ from pre_commit.util import win_exe
16
+ from pre_commit.yaml import yaml_load
17
+
18
+ ENVIRONMENT_DIR = 'dartenv'
19
+
20
+ get_default_version = lang_base.basic_get_default_version
21
+ health_check = lang_base.basic_health_check
22
+ run_hook = lang_base.basic_run_hook
23
+
24
+
25
+ def get_env_patch(venv: str) -> PatchesT:
26
+ return (
27
+ ('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))),
28
+ )
29
+
30
+
31
+ @contextlib.contextmanager
32
+ def in_env(prefix: Prefix, version: str) -> Generator[None]:
33
+ envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
34
+ with envcontext(get_env_patch(envdir)):
35
+ yield
36
+
37
+
38
+ def install_environment(
39
+ prefix: Prefix,
40
+ version: str,
41
+ additional_dependencies: Sequence[str],
42
+ ) -> None:
43
+ lang_base.assert_version_default('dart', version)
44
+
45
+ envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
46
+ bin_dir = os.path.join(envdir, 'bin')
47
+
48
+ def _install_dir(prefix_p: Prefix, pub_cache: str) -> None:
49
+ dart_env = {**os.environ, 'PUB_CACHE': pub_cache}
50
+
51
+ with open(prefix_p.path('pubspec.yaml')) as f:
52
+ pubspec_contents = yaml_load(f)
53
+
54
+ lang_base.setup_cmd(prefix_p, ('dart', 'pub', 'get'), env=dart_env)
55
+
56
+ for executable in pubspec_contents['executables']:
57
+ lang_base.setup_cmd(
58
+ prefix_p,
59
+ (
60
+ 'dart', 'compile', 'exe',
61
+ '--output', os.path.join(bin_dir, win_exe(executable)),
62
+ prefix_p.path('bin', f'{executable}.dart'),
63
+ ),
64
+ env=dart_env,
65
+ )
66
+
67
+ os.makedirs(bin_dir)
68
+
69
+ with tempfile.TemporaryDirectory() as tmp:
70
+ _install_dir(prefix, tmp)
71
+
72
+ for dep_s in additional_dependencies:
73
+ with tempfile.TemporaryDirectory() as dep_tmp:
74
+ dep, _, version = dep_s.partition(':')
75
+ if version:
76
+ dep_cmd: tuple[str, ...] = (dep, '--version', version)
77
+ else:
78
+ dep_cmd = (dep,)
79
+
80
+ lang_base.setup_cmd(
81
+ prefix,
82
+ ('dart', 'pub', 'cache', 'add', *dep_cmd),
83
+ env={**os.environ, 'PUB_CACHE': dep_tmp},
84
+ )
85
+
86
+ # try and find the 'pubspec.yaml' that just got added
87
+ for root, _, filenames in os.walk(dep_tmp):
88
+ if 'pubspec.yaml' in filenames:
89
+ with tempfile.TemporaryDirectory() as copied:
90
+ pkg = os.path.join(copied, 'pkg')
91
+ shutil.copytree(root, pkg)
92
+ _install_dir(Prefix(pkg), dep_tmp)
93
+ break
94
+ else:
95
+ raise AssertionError(
96
+ f'could not find pubspec.yaml for {dep_s}',
97
+ )
@@ -0,0 +1,181 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import functools
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import re
9
+ from collections.abc import Sequence
10
+
11
+ from pre_commit import lang_base
12
+ from pre_commit.prefix import Prefix
13
+ from pre_commit.util import CalledProcessError
14
+ from pre_commit.util import cmd_output_b
15
+
16
+ ENVIRONMENT_DIR = 'docker'
17
+ PRE_COMMIT_LABEL = 'PRE_COMMIT'
18
+ get_default_version = lang_base.basic_get_default_version
19
+ health_check = lang_base.basic_health_check
20
+ in_env = lang_base.no_env # no special environment for docker
21
+
22
+ _HOSTNAME_MOUNT_RE = re.compile(
23
+ rb"""
24
+ /containers
25
+ (?:/overlay-containers)?
26
+ /([a-z0-9]{64})
27
+ (?:/userdata)?
28
+ /hostname
29
+ """,
30
+ re.VERBOSE,
31
+ )
32
+
33
+
34
+ def _get_container_id() -> str | None:
35
+ with contextlib.suppress(FileNotFoundError):
36
+ with open('/proc/1/mountinfo', 'rb') as f:
37
+ for line in f:
38
+ m = _HOSTNAME_MOUNT_RE.search(line)
39
+ if m:
40
+ return m[1].decode()
41
+
42
+ return None
43
+
44
+
45
+ def _get_docker_path(path: str) -> str:
46
+ container_id = _get_container_id()
47
+ if container_id is None:
48
+ return path
49
+
50
+ try:
51
+ _, out, _ = cmd_output_b('docker', 'inspect', container_id)
52
+ except CalledProcessError:
53
+ # self-container was not visible from here (perhaps docker-in-docker)
54
+ return path
55
+
56
+ container, = json.loads(out)
57
+ for mount in container['Mounts']:
58
+ src_path = mount['Source']
59
+ to_path = mount['Destination']
60
+ if os.path.commonpath((path, to_path)) == to_path:
61
+ # So there is something in common,
62
+ # and we can proceed remapping it
63
+ return path.replace(to_path, src_path)
64
+ # we're in Docker, but the path is not mounted, cannot really do anything,
65
+ # so fall back to original path
66
+ return path
67
+
68
+
69
+ def md5(s: str) -> str: # pragma: win32 no cover
70
+ return hashlib.md5(s.encode()).hexdigest()
71
+
72
+
73
+ def docker_tag(prefix: Prefix) -> str: # pragma: win32 no cover
74
+ md5sum = md5(os.path.basename(prefix.prefix_dir)).lower()
75
+ return f'pre-commit-{md5sum}'
76
+
77
+
78
+ def build_docker_image(
79
+ prefix: Prefix,
80
+ *,
81
+ pull: bool,
82
+ ) -> None: # pragma: win32 no cover
83
+ cmd: tuple[str, ...] = (
84
+ 'docker', 'build',
85
+ '--tag', docker_tag(prefix),
86
+ '--label', PRE_COMMIT_LABEL,
87
+ )
88
+ if pull:
89
+ cmd += ('--pull',)
90
+ # This must come last for old versions of docker. See #477
91
+ cmd += ('.',)
92
+ lang_base.setup_cmd(prefix, cmd)
93
+
94
+
95
+ def install_environment(
96
+ prefix: Prefix, version: str, additional_dependencies: Sequence[str],
97
+ ) -> None: # pragma: win32 no cover
98
+ lang_base.assert_version_default('docker', version)
99
+ lang_base.assert_no_additional_deps('docker', additional_dependencies)
100
+
101
+ directory = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
102
+
103
+ # Docker doesn't really have relevant disk environment, but pre-commit
104
+ # still needs to cleanup its state files on failure
105
+ build_docker_image(prefix, pull=True)
106
+ os.mkdir(directory)
107
+
108
+
109
+ @functools.lru_cache(maxsize=1)
110
+ def _is_rootless() -> bool: # pragma: win32 no cover
111
+ retcode, out, _ = cmd_output_b(
112
+ 'docker', 'system', 'info', '--format', '{{ json . }}',
113
+ )
114
+ if retcode != 0:
115
+ return False
116
+
117
+ info = json.loads(out)
118
+ try:
119
+ return (
120
+ # docker:
121
+ # https://docs.docker.com/reference/api/engine/version/v1.48/#tag/System/operation/SystemInfo
122
+ 'name=rootless' in (info.get('SecurityOptions') or ()) or
123
+ # podman:
124
+ # https://docs.podman.io/en/latest/_static/api.html?version=v5.4#tag/system/operation/SystemInfoLibpod
125
+ info['host']['security']['rootless']
126
+ )
127
+ except KeyError:
128
+ return False
129
+
130
+
131
+ def get_docker_user() -> tuple[str, ...]: # pragma: win32 no cover
132
+ if _is_rootless():
133
+ return ()
134
+
135
+ try:
136
+ return ('-u', f'{os.getuid()}:{os.getgid()}')
137
+ except AttributeError:
138
+ return ()
139
+
140
+
141
+ def get_docker_tty(*, color: bool) -> tuple[str, ...]: # pragma: win32 no cover # noqa: E501
142
+ return (('--tty',) if color else ())
143
+
144
+
145
+ def docker_cmd(*, color: bool) -> tuple[str, ...]: # pragma: win32 no cover
146
+ return (
147
+ 'docker', 'run',
148
+ '--rm',
149
+ *get_docker_tty(color=color),
150
+ *get_docker_user(),
151
+ # https://docs.docker.com/engine/reference/commandline/run/#mount-volumes-from-container-volumes-from
152
+ # The `Z` option tells Docker to label the content with a private
153
+ # unshared label. Only the current container can use a private volume.
154
+ '-v', f'{_get_docker_path(os.getcwd())}:/src:rw,Z',
155
+ '--workdir', '/src',
156
+ )
157
+
158
+
159
+ def run_hook(
160
+ prefix: Prefix,
161
+ entry: str,
162
+ args: Sequence[str],
163
+ file_args: Sequence[str],
164
+ *,
165
+ is_local: bool,
166
+ require_serial: bool,
167
+ color: bool,
168
+ ) -> tuple[int, bytes]: # pragma: win32 no cover
169
+ # Rebuild the docker image in case it has gone missing, as many people do
170
+ # automated cleanup of docker images.
171
+ build_docker_image(prefix, pull=False)
172
+
173
+ entry_exe, *cmd_rest = lang_base.hook_cmd(entry, args)
174
+
175
+ entry_tag = ('--entrypoint', entry_exe, docker_tag(prefix))
176
+ return lang_base.run_xargs(
177
+ (*docker_cmd(color=color), *entry_tag, *cmd_rest),
178
+ file_args,
179
+ require_serial=require_serial,
180
+ color=color,
181
+ )
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+
5
+ from pre_commit import lang_base
6
+ from pre_commit.languages.docker import docker_cmd
7
+ from pre_commit.prefix import Prefix
8
+
9
+ ENVIRONMENT_DIR = None
10
+ get_default_version = lang_base.basic_get_default_version
11
+ health_check = lang_base.basic_health_check
12
+ install_environment = lang_base.no_install
13
+ in_env = lang_base.no_env
14
+
15
+
16
+ def run_hook(
17
+ prefix: Prefix,
18
+ entry: str,
19
+ args: Sequence[str],
20
+ file_args: Sequence[str],
21
+ *,
22
+ is_local: bool,
23
+ require_serial: bool,
24
+ color: bool,
25
+ ) -> tuple[int, bytes]: # pragma: win32 no cover
26
+ cmd = docker_cmd(color=color) + lang_base.hook_cmd(entry, args)
27
+ return lang_base.run_xargs(
28
+ cmd,
29
+ file_args,
30
+ require_serial=require_serial,
31
+ color=color,
32
+ )
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import os.path
5
+ import re
6
+ import tempfile
7
+ import xml.etree.ElementTree
8
+ import zipfile
9
+ from collections.abc import Generator
10
+ from collections.abc import Sequence
11
+
12
+ from pre_commit import lang_base
13
+ from pre_commit.envcontext import envcontext
14
+ from pre_commit.envcontext import PatchesT
15
+ from pre_commit.envcontext import Var
16
+ from pre_commit.prefix import Prefix
17
+
18
+ ENVIRONMENT_DIR = 'dotnetenv'
19
+ BIN_DIR = 'bin'
20
+
21
+ get_default_version = lang_base.basic_get_default_version
22
+ health_check = lang_base.basic_health_check
23
+ run_hook = lang_base.basic_run_hook
24
+
25
+
26
+ def get_env_patch(venv: str) -> PatchesT:
27
+ return (
28
+ ('PATH', (os.path.join(venv, BIN_DIR), os.pathsep, Var('PATH'))),
29
+ )
30
+
31
+
32
+ @contextlib.contextmanager
33
+ def in_env(prefix: Prefix, version: str) -> Generator[None]:
34
+ envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
35
+ with envcontext(get_env_patch(envdir)):
36
+ yield
37
+
38
+
39
+ @contextlib.contextmanager
40
+ def _nuget_config_no_sources() -> Generator[str]:
41
+ with tempfile.TemporaryDirectory() as tmpdir:
42
+ nuget_config = os.path.join(tmpdir, 'nuget.config')
43
+ with open(nuget_config, 'w') as f:
44
+ f.write(
45
+ '<?xml version="1.0" encoding="utf-8"?>'
46
+ '<configuration>'
47
+ ' <packageSources>'
48
+ ' <clear />'
49
+ ' </packageSources>'
50
+ '</configuration>',
51
+ )
52
+ yield nuget_config
53
+
54
+
55
+ def install_environment(
56
+ prefix: Prefix,
57
+ version: str,
58
+ additional_dependencies: Sequence[str],
59
+ ) -> None:
60
+ lang_base.assert_version_default('dotnet', version)
61
+ lang_base.assert_no_additional_deps('dotnet', additional_dependencies)
62
+
63
+ envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
64
+ build_dir = prefix.path('pre-commit-build')
65
+
66
+ # Build & pack nupkg file
67
+ lang_base.setup_cmd(
68
+ prefix,
69
+ (
70
+ 'dotnet', 'pack',
71
+ '--configuration', 'Release',
72
+ '--property', f'PackageOutputPath={build_dir}',
73
+ ),
74
+ )
75
+
76
+ nupkg_dir = prefix.path(build_dir)
77
+ nupkgs = [x for x in os.listdir(nupkg_dir) if x.endswith('.nupkg')]
78
+
79
+ if not nupkgs:
80
+ raise AssertionError('could not find any build outputs to install')
81
+
82
+ for nupkg in nupkgs:
83
+ with zipfile.ZipFile(os.path.join(nupkg_dir, nupkg)) as f:
84
+ nuspec, = (x for x in f.namelist() if x.endswith('.nuspec'))
85
+ with f.open(nuspec) as spec:
86
+ tree = xml.etree.ElementTree.parse(spec)
87
+
88
+ namespace = re.match(r'{.*}', tree.getroot().tag)
89
+ if not namespace:
90
+ raise AssertionError('could not parse namespace from nuspec')
91
+
92
+ tool_id_element = tree.find(f'.//{namespace[0]}id')
93
+ if tool_id_element is None:
94
+ raise AssertionError('expected to find an "id" element')
95
+
96
+ tool_id = tool_id_element.text
97
+ if not tool_id:
98
+ raise AssertionError('"id" element missing tool name')
99
+
100
+ # Install to bin dir
101
+ with _nuget_config_no_sources() as nuget_config:
102
+ lang_base.setup_cmd(
103
+ prefix,
104
+ (
105
+ 'dotnet', 'tool', 'install',
106
+ '--configfile', nuget_config,
107
+ '--tool-path', os.path.join(envdir, BIN_DIR),
108
+ '--add-source', build_dir,
109
+ tool_id,
110
+ ),
111
+ )
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+
5
+ from pre_commit import lang_base
6
+ from pre_commit.prefix import Prefix
7
+
8
+ ENVIRONMENT_DIR = None
9
+ get_default_version = lang_base.basic_get_default_version
10
+ health_check = lang_base.basic_health_check
11
+ install_environment = lang_base.no_install
12
+ in_env = lang_base.no_env
13
+
14
+
15
+ def run_hook(
16
+ prefix: Prefix,
17
+ entry: str,
18
+ args: Sequence[str],
19
+ file_args: Sequence[str],
20
+ *,
21
+ is_local: bool,
22
+ require_serial: bool,
23
+ color: bool,
24
+ ) -> tuple[int, bytes]:
25
+ out = f'{entry}\n\n'.encode()
26
+ out += b'\n'.join(f.encode() for f in file_args) + b'\n'
27
+ return 1, out