Skip to content

Commit 2cfd281

Browse files
committed
Add pcre type.
1 parent 3867394 commit 2cfd281

File tree

11 files changed

+184
-10
lines changed

11 files changed

+184
-10
lines changed

pre_commit/languages/all.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import unicode_literals
22

33
from pre_commit.languages import node
4+
from pre_commit.languages import pcre
45
from pre_commit.languages import python
56
from pre_commit.languages import ruby
67
from pre_commit.languages import script
@@ -36,6 +37,7 @@
3637

3738
languages = {
3839
'node': node,
40+
'pcre': pcre,
3941
'python': python,
4042
'ruby': ruby,
4143
'script': script,

pre_commit/languages/helpers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
from __future__ import unicode_literals
22

33

4+
def file_args_to_stdin(file_args):
5+
return '\n'.join(list(file_args) + [''])
6+
7+
48
def run_hook(env, hook, file_args):
59
return env.run(
610
' '.join(['xargs', hook['entry']] + hook['args']),
7-
stdin='\n'.join(list(file_args) + ['']),
11+
stdin=file_args_to_stdin(file_args),
812
retcode=None,
913
)
1014

pre_commit/languages/pcre.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from __future__ import unicode_literals
2+
3+
from pre_commit.languages.helpers import file_args_to_stdin
4+
from pre_commit.util import shell_escape
5+
6+
7+
ENVIRONMENT_DIR = None
8+
9+
10+
def install_environment(repo_cmd_runner, version='default'):
11+
"""Installation for pcre type is a noop."""
12+
raise AssertionError('Cannot install pcre repo.')
13+
14+
15+
def run_hook(repo_cmd_runner, hook, file_args):
16+
# For PCRE the entry is the regular expression to match
17+
return repo_cmd_runner.run(
18+
[
19+
'xargs', 'sh', '-c',
20+
# Grep usually returns 0 for matches, and nonzero for non-matches
21+
# so we flip it here.
22+
'! grep -H -n -P {0} $@'.format(shell_escape(hook['entry'])),
23+
'--',
24+
],
25+
stdin=file_args_to_stdin(file_args),
26+
retcode=None,
27+
)

pre_commit/languages/script.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
from __future__ import unicode_literals
22

3+
from pre_commit.languages.helpers import file_args_to_stdin
4+
5+
36
ENVIRONMENT_DIR = None
47

58

69
def install_environment(repo_cmd_runner, version='default'):
710
"""Installation for script type is a noop."""
11+
raise AssertionError('Cannot install script repo.')
812

913

1014
def run_hook(repo_cmd_runner, hook, file_args):
1115
return repo_cmd_runner.run(
1216
['xargs', '{{prefix}}{0}'.format(hook['entry'])] + hook['args'],
1317
# TODO: this is duplicated in pre_commit/languages/helpers.py
14-
stdin='\n'.join(list(file_args) + ['']),
18+
stdin=file_args_to_stdin(file_args),
1519
retcode=None,
1620
)

pre_commit/languages/system.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22

33
import shlex
44

5+
from pre_commit.languages.helpers import file_args_to_stdin
6+
57

68
ENVIRONMENT_DIR = None
79

810

911
def install_environment(repo_cmd_runner, version='default'):
1012
"""Installation for system type is a noop."""
13+
raise AssertionError('Cannot install system repo.')
1114

1215

1316
def run_hook(repo_cmd_runner, hook, file_args):
1417
return repo_cmd_runner.run(
1518
['xargs'] + shlex.split(hook['entry']) + hook['args'],
16-
# TODO: this is duplicated in pre_commit/languages/helpers.py
17-
stdin='\n'.join(list(file_args) + ['']),
19+
stdin=file_args_to_stdin(file_args),
1820
retcode=None,
1921
)

pre_commit/util.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ def clean_path_on_failure(path):
4949
raise
5050

5151

52-
# TODO: asottile.contextlib this with a forward port of nested
5352
@contextlib.contextmanager
5453
def noop_context():
5554
yield
55+
56+
57+
def shell_escape(arg):
58+
return "'" + arg.replace("'", "'\"'\"'".strip()) + "'"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
- id: regex-with-quotes
2+
name: Regex with quotes
3+
entry: "foo'bar"
4+
language: pcre
5+
files: ''
6+
- id: other-regex
7+
name: Other regex
8+
entry: ^\[INFO\]
9+
language: pcre
10+
files: ''

tests/languages/all_test.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11
from __future__ import unicode_literals
22

3+
import inspect
34
import pytest
45

56
from pre_commit.languages.all import all_languages
67
from pre_commit.languages.all import languages
78

89

910
@pytest.mark.parametrize('language', all_languages)
10-
def test_all_languages_support_interface(language):
11-
assert hasattr(languages[language], 'install_environment')
12-
assert hasattr(languages[language], 'run_hook')
11+
def test_install_environment_argspec(language):
12+
expected_argspec = inspect.ArgSpec(
13+
args=['repo_cmd_runner', 'version'],
14+
varargs=None,
15+
keywords=None,
16+
defaults=('default',),
17+
)
18+
argspec = inspect.getargspec(languages[language].install_environment)
19+
assert argspec == expected_argspec
20+
21+
22+
@pytest.mark.parametrize('language', all_languages)
23+
def test_ENVIRONMENT_DIR(language):
1324
assert hasattr(languages[language], 'ENVIRONMENT_DIR')
25+
26+
27+
@pytest.mark.parametrize('language', all_languages)
28+
def test_run_hook_argpsec(language):
29+
expected_argspec = inspect.ArgSpec(
30+
args=['repo_cmd_runner', 'hook', 'file_args'],
31+
varargs=None, keywords=None, defaults=None,
32+
)
33+
argspec = inspect.getargspec(languages[language].run_hook)
34+
assert argspec == expected_argspec

tests/languages/helpers_test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from __future__ import absolute_import
2+
from __future__ import unicode_literals
3+
4+
from pre_commit.languages.helpers import file_args_to_stdin
5+
6+
7+
def test_file_args_to_stdin_empty():
8+
assert file_args_to_stdin([]) == ''
9+
10+
11+
def test_file_args_to_stdin_some():
12+
assert file_args_to_stdin(['foo', 'bar']) == 'foo\nbar\n'
13+
14+
15+
def test_file_args_to_stdin_tuple():
16+
assert file_args_to_stdin(('foo', 'bar')) == 'foo\nbar\n'

tests/repository_test.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import absolute_import
22
from __future__ import unicode_literals
33

4+
import io
45
import mock
56
import os.path
67
import pytest
@@ -25,12 +26,20 @@ def test_install_python_repo_in_env(tmpdir_factory, store):
2526
assert os.path.exists(os.path.join(store.directory, repo.sha, 'py_env'))
2627

2728

28-
def _test_hook_repo(tmpdir_factory, store, repo_path, hook_id, args, expected):
29+
def _test_hook_repo(
30+
tmpdir_factory,
31+
store,
32+
repo_path,
33+
hook_id,
34+
args,
35+
expected,
36+
expected_return_code=0,
37+
):
2938
path = make_repo(tmpdir_factory, repo_path)
3039
config = make_config_from_repo(path)
3140
repo = Repository.create(config, store)
3241
ret = repo.run_hook(hook_id, args)
33-
assert ret[0] == 0
42+
assert ret[0] == expected_return_code
3443
assert ret[1] == expected
3544

3645

@@ -102,6 +111,69 @@ def test_run_a_script_hook(tmpdir_factory, store):
102111
)
103112

104113

114+
@pytest.mark.integration
115+
def test_pcre_hook_no_match(tmpdir_factory, store):
116+
path = git_dir(tmpdir_factory)
117+
with local.cwd(path):
118+
with io.open('herp', 'w') as herp:
119+
herp.write('foo')
120+
121+
with io.open('derp', 'w') as derp:
122+
derp.write('bar')
123+
124+
_test_hook_repo(
125+
tmpdir_factory, store, 'pcre_hooks_repo',
126+
'regex-with-quotes', ['herp', 'derp'], '',
127+
)
128+
129+
_test_hook_repo(
130+
tmpdir_factory, store, 'pcre_hooks_repo',
131+
'other-regex', ['herp', 'derp'], '',
132+
)
133+
134+
135+
@pytest.mark.integration
136+
def test_pcre_hook_matching(tmpdir_factory, store):
137+
path = git_dir(tmpdir_factory)
138+
with local.cwd(path):
139+
with io.open('herp', 'w') as herp:
140+
herp.write("\nherpfoo'bard\n")
141+
142+
with io.open('derp', 'w') as derp:
143+
derp.write('[INFO] information yo\n')
144+
145+
_test_hook_repo(
146+
tmpdir_factory, store, 'pcre_hooks_repo',
147+
'regex-with-quotes', ['herp', 'derp'], "herp:2:herpfoo'bard\n",
148+
expected_return_code=123,
149+
)
150+
151+
_test_hook_repo(
152+
tmpdir_factory, store, 'pcre_hooks_repo',
153+
'other-regex', ['herp', 'derp'], 'derp:1:[INFO] information yo\n',
154+
expected_return_code=123,
155+
)
156+
157+
158+
@pytest.mark.integration
159+
def test_pcre_many_files(tmpdir_factory, store):
160+
# This is intended to simulate lots of passing files and one failing file
161+
# to make sure it still fails. This is not the case when naively using
162+
# a system hook with `grep -H -n '...'` and expected_return_code=123.
163+
path = git_dir(tmpdir_factory)
164+
with local.cwd(path):
165+
with io.open('herp', 'w') as herp:
166+
herp.write('[INFO] info\n')
167+
168+
_test_hook_repo(
169+
tmpdir_factory, store, 'pcre_hooks_repo',
170+
'other-regex',
171+
['/dev/null'] * 15000 + ['herp'],
172+
'herp:1:[INFO] info\n',
173+
expected_return_code=123,
174+
)
175+
176+
105177
@pytest.mark.integration
106178
def test_cwd_of_hook(tmpdir_factory, store):
107179
# Note: this doubles as a test for `system` hooks

0 commit comments

Comments
 (0)