From a932315a15fdc7903b2fadb2d68a5ef28e580728 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Mon, 21 Mar 2016 19:56:41 -0700 Subject: [PATCH] Implement 'negate' to simplify pcre --- pre_commit/languages/pcre.py | 26 ++++++++------------------ pre_commit/util.py | 4 ---- pre_commit/xargs.py | 22 ++++++++++++++++++---- tests/util_test.py | 13 ------------- tests/xargs_test.py | 25 +++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 39 deletions(-) diff --git a/pre_commit/languages/pcre.py b/pre_commit/languages/pcre.py index 0b2cfd18..faba1da0 100644 --- a/pre_commit/languages/pcre.py +++ b/pre_commit/languages/pcre.py @@ -2,7 +2,6 @@ from __future__ import unicode_literals from sys import platform -from pre_commit.util import shell_escape from pre_commit.xargs import xargs @@ -19,21 +18,12 @@ def install_environment( def run_hook(repo_cmd_runner, hook, file_args): - grep_command = '{0} -H -n -P'.format( - 'ggrep' if platform == 'darwin' else 'grep', - ) - # For PCRE the entry is the regular expression to match - return xargs( - ( - 'sh', '-c', - # Grep usually returns 0 for matches, and nonzero for non-matches - # so we flip it here. - '! {0} {1} {2} $@'.format( - grep_command, ' '.join(hook['args']), - shell_escape(hook['entry']), - ), - '--', - ), - file_args, - ) + cmd = ( + 'ggrep' if platform == 'darwin' else 'grep', + '-H', '-n', '-P', + ) + tuple(hook['args']) + (hook['entry'],) + + # Grep usually returns 0 for matches, and nonzero for non-matches so we + # negate it here. + return xargs(cmd, file_args, negate=True) diff --git a/pre_commit/util.py b/pre_commit/util.py index 853a95b1..046cf96e 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -67,10 +67,6 @@ def noop_context(): yield -def shell_escape(arg): - return "'" + arg.replace("'", "'\"'\"'".strip()) + "'" - - def no_git_env(): # Too many bugs dealing with environment variables and GIT: # https://github.com/pre-commit/pre-commit/issues/300 diff --git a/pre_commit/xargs.py b/pre_commit/xargs.py index 2b8aff56..e0b87299 100644 --- a/pre_commit/xargs.py +++ b/pre_commit/xargs.py @@ -42,17 +42,31 @@ def partition(cmd, varargs, _max_length=MAX_LENGTH): return tuple(ret) -def xargs(cmd, varargs): - """A simplified implementation of xargs.""" +def xargs(cmd, varargs, **kwargs): + """A simplified implementation of xargs. + + negate: Make nonzero successful and zero a failure + """ + negate = kwargs.pop('negate', False) retcode = 0 stdout = b'' stderr = b'' - for run_cmd in partition(cmd, varargs): + for run_cmd in partition(cmd, varargs, **kwargs): proc_retcode, proc_out, proc_err = cmd_output( *run_cmd, encoding=None, retcode=None ) - retcode |= proc_retcode + # This is *slightly* too clever so I'll explain it. + # First the xor boolean table: + # T | F | + # +-------+ + # T | F | T | + # --+-------+ + # F | T | F | + # --+-------+ + # When negate is True, it has the effect of flipping the return code + # Otherwise, the retuncode is unchanged + retcode |= bool(proc_retcode) ^ negate stdout += proc_out stderr += proc_err diff --git a/tests/util_test.py b/tests/util_test.py index 1361d639..7fa25bcd 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -9,7 +9,6 @@ import pytest from pre_commit.util import clean_path_on_failure from pre_commit.util import cwd from pre_commit.util import memoize_by_cwd -from pre_commit.util import shell_escape from pre_commit.util import tmpdir @@ -79,18 +78,6 @@ def test_clean_path_on_failure_cleans_for_system_exit(in_tmpdir): assert not os.path.exists('foo') -@pytest.mark.parametrize( - ('input_str', 'expected'), - ( - ('', "''"), - ('foo"bar', "'foo\"bar'"), - ("foo'bar", "'foo'\"'\"'bar'") - ), -) -def test_shell_escape(input_str, expected): - assert shell_escape(input_str) == expected - - def test_tmpdir(): with tmpdir() as tempdir: assert os.path.exists(tempdir) diff --git a/tests/xargs_test.py b/tests/xargs_test.py index 239f1a2d..cb27f62b 100644 --- a/tests/xargs_test.py +++ b/tests/xargs_test.py @@ -45,3 +45,28 @@ def test_xargs_smoke(): assert ret == 0 assert out == b'hello world\n' assert err == b'' + + +exit_cmd = ('bash', '-c', 'exit $1', '--') +# Abuse max_length to control the exit code +max_length = len(' '.join(exit_cmd)) + 2 + + +def test_xargs_negate(): + ret, _, _ = xargs.xargs( + exit_cmd, ('1',), negate=True, _max_length=max_length, + ) + assert ret == 0 + + ret, _, _ = xargs.xargs( + exit_cmd, ('1', '0'), negate=True, _max_length=max_length, + ) + assert ret == 1 + + +def test_xargs_retcode_normal(): + ret, _, _ = xargs.xargs(exit_cmd, ('0',), _max_length=max_length) + assert ret == 0 + + ret, _, _ = xargs.xargs(exit_cmd, ('0', '1'), _max_length=max_length) + assert ret == 1