Skip to content

Commit b7d3954

Browse files
committed
Implement a simplified xargs in python
1 parent a5b56bd commit b7d3954

File tree

13 files changed

+130
-62
lines changed

13 files changed

+130
-62
lines changed

pre_commit/commands/run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def _run_single_hook(hook, repo, args, write, skips=frozenset()):
8686
sys.stdout.flush()
8787

8888
diff_before = cmd_output('git', 'diff', retcode=None, encoding=None)
89-
retcode, stdout, stderr = repo.run_hook(hook, filenames)
89+
retcode, stdout, stderr = repo.run_hook(hook, tuple(filenames))
9090
diff_after = cmd_output('git', 'diff', retcode=None, encoding=None)
9191

9292
file_modifications = diff_before != diff_after

pre_commit/languages/helpers.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,3 @@ def environment_dir(ENVIRONMENT_DIR, language_version):
1212
return None
1313
else:
1414
return '{0}-{1}'.format(ENVIRONMENT_DIR, language_version)
15-
16-
17-
def file_args_to_stdin(file_args):
18-
return '\0'.join(list(file_args) + [''])
19-
20-
21-
def run_hook(cmd_args, file_args):
22-
return cmd_output(
23-
# Use -s 4000 (slightly less than posix mandated minimum)
24-
# This is to prevent "xargs: ... Bad file number" on windows
25-
'xargs', '-0', '-s4000', *cmd_args,
26-
stdin=file_args_to_stdin(file_args),
27-
retcode=None,
28-
encoding=None
29-
)

pre_commit/languages/node.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pre_commit.envcontext import Var
99
from pre_commit.languages import helpers
1010
from pre_commit.util import clean_path_on_failure
11+
from pre_commit.xargs import xargs
1112

1213

1314
ENVIRONMENT_DIR = 'node_env'
@@ -63,6 +64,4 @@ def install_environment(
6364

6465
def run_hook(repo_cmd_runner, hook, file_args):
6566
with in_env(repo_cmd_runner, hook['language_version']):
66-
return helpers.run_hook(
67-
(hook['entry'],) + tuple(hook['args']), file_args,
68-
)
67+
return xargs((hook['entry'],) + tuple(hook['args']), file_args)

pre_commit/languages/pcre.py

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

33
from sys import platform
44

5-
from pre_commit.languages import helpers
65
from pre_commit.util import shell_escape
6+
from pre_commit.xargs import xargs
77

88

99
ENVIRONMENT_DIR = None
@@ -24,7 +24,7 @@ def run_hook(repo_cmd_runner, hook, file_args):
2424
)
2525

2626
# For PCRE the entry is the regular expression to match
27-
return helpers.run_hook(
27+
return xargs(
2828
(
2929
'sh', '-c',
3030
# Grep usually returns 0 for matches, and nonzero for non-matches

pre_commit/languages/python.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pre_commit.envcontext import Var
1111
from pre_commit.languages import helpers
1212
from pre_commit.util import clean_path_on_failure
13+
from pre_commit.xargs import xargs
1314

1415

1516
ENVIRONMENT_DIR = 'py_env'
@@ -80,6 +81,4 @@ def install_environment(
8081

8182
def run_hook(repo_cmd_runner, hook, file_args):
8283
with in_env(repo_cmd_runner, hook['language_version']):
83-
return helpers.run_hook(
84-
(hook['entry'],) + tuple(hook['args']), file_args,
85-
)
84+
return xargs((hook['entry'],) + tuple(hook['args']), file_args)

pre_commit/languages/ruby.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from pre_commit.util import clean_path_on_failure
1313
from pre_commit.util import resource_filename
1414
from pre_commit.util import tarfile_open
15+
from pre_commit.xargs import xargs
1516

1617

1718
ENVIRONMENT_DIR = 'rbenv'
@@ -125,6 +126,4 @@ def install_environment(
125126

126127
def run_hook(repo_cmd_runner, hook, file_args):
127128
with in_env(repo_cmd_runner, hook['language_version']):
128-
return helpers.run_hook(
129-
(hook['entry'],) + tuple(hook['args']), file_args,
130-
)
129+
return xargs((hook['entry'],) + tuple(hook['args']), file_args)

pre_commit/languages/script.py

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

3-
from pre_commit.languages import helpers
3+
from pre_commit.xargs import xargs
44

55

66
ENVIRONMENT_DIR = None
@@ -16,7 +16,7 @@ def install_environment(
1616

1717

1818
def run_hook(repo_cmd_runner, hook, file_args):
19-
return helpers.run_hook(
19+
return xargs(
2020
(repo_cmd_runner.prefix_dir + hook['entry'],) + tuple(hook['args']),
2121
file_args,
2222
)

pre_commit/languages/system.py

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

33
import shlex
44

5-
from pre_commit.languages import helpers
5+
from pre_commit.xargs import xargs
66

77

88
ENVIRONMENT_DIR = None
@@ -18,6 +18,6 @@ def install_environment(
1818

1919

2020
def run_hook(repo_cmd_runner, hook, file_args):
21-
return helpers.run_hook(
21+
return xargs(
2222
tuple(shlex.split(hook['entry'])) + tuple(hook['args']), file_args,
2323
)

pre_commit/util.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ def to_text(self):
160160

161161
def cmd_output(*cmd, **kwargs):
162162
retcode = kwargs.pop('retcode', 0)
163-
stdin = kwargs.pop('stdin', None)
164163
encoding = kwargs.pop('encoding', 'UTF-8')
165164
__popen = kwargs.pop('__popen', subprocess.Popen)
166165

@@ -170,9 +169,6 @@ def cmd_output(*cmd, **kwargs):
170169
'stderr': subprocess.PIPE,
171170
}
172171

173-
if stdin is not None:
174-
stdin = stdin.encode('UTF-8')
175-
176172
# py2/py3 on windows are more strict about the types here
177173
cmd = [five.n(arg) for arg in cmd]
178174
kwargs['env'] = dict(
@@ -182,7 +178,7 @@ def cmd_output(*cmd, **kwargs):
182178

183179
popen_kwargs.update(kwargs)
184180
proc = __popen(cmd, **popen_kwargs)
185-
stdout, stderr = proc.communicate(stdin)
181+
stdout, stderr = proc.communicate()
186182
if encoding is not None and stdout is not None:
187183
stdout = stdout.decode(encoding)
188184
if encoding is not None and stderr is not None:

pre_commit/xargs.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from __future__ import absolute_import
2+
from __future__ import unicode_literals
3+
4+
from pre_commit.util import cmd_output
5+
6+
7+
# Limit used previously to avoid "xargs ... Bad file number" on windows
8+
# This is slightly less than the posix mandated minimum
9+
MAX_LENGTH = 4000
10+
11+
12+
class ArgumentTooLongError(RuntimeError):
13+
pass
14+
15+
16+
def partition(cmd, varargs, _max_length=MAX_LENGTH):
17+
cmd = tuple(cmd)
18+
ret = []
19+
20+
ret_cmd = []
21+
total_len = len(' '.join(cmd))
22+
# Reversed so arguments are in order
23+
varargs = list(reversed(varargs))
24+
25+
while varargs:
26+
arg = varargs.pop()
27+
28+
if total_len + 1 + len(arg) <= _max_length:
29+
ret_cmd.append(arg)
30+
total_len += len(arg)
31+
elif not ret_cmd:
32+
raise ArgumentTooLongError(arg)
33+
else:
34+
# We've exceeded the length, yield a command
35+
ret.append(cmd + tuple(ret_cmd))
36+
ret_cmd = []
37+
total_len = len(' '.join(cmd))
38+
varargs.append(arg)
39+
40+
ret.append(cmd + tuple(ret_cmd))
41+
42+
return tuple(ret)
43+
44+
45+
def xargs(cmd, varargs):
46+
"""A simplified implementation of xargs."""
47+
retcode = 0
48+
stdout = b''
49+
stderr = b''
50+
51+
for run_cmd in partition(cmd, varargs):
52+
proc_retcode, proc_out, proc_err = cmd_output(
53+
*run_cmd, encoding=None, retcode=None
54+
)
55+
retcode |= proc_retcode
56+
stdout += proc_out
57+
stderr += proc_err
58+
59+
return retcode, stdout, stderr

0 commit comments

Comments
 (0)