prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Add option to pass additional dependencies to hooks
  I am currently working on implementing this framework and one of the things I am trying to run is eslint. As part of that I have a number of plugins that are in my configuration file. I think that, rather than forcing anyone who is using plugins to create a new hook definition with a corresponding package.json it might be useful to add a global option to pass a list of dependencies in the configuration file.
  
  For instance, something lilke this:
  
  ``` yaml
  -   repo: https://github.com/pre-commit/mirrors-eslint
      sha: 135f285caf8e6e886b28c8e98fdff402b69c4490
      hooks:
      -   id: eslint
          language_version: '0.12.7'
          dependencies: [eslint-plugin-react, eslint-plugin-html]
  ```
  
  and have those dependencies installed into the generated environment for that language.
  
  I am going to work on implementing this in my forked repo but would like feedback on whether this is a desired feature or any implementation advice on how best to facilitate this.
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `pre_commit/output.py`
  Content:
  ```
  1 from __future__ import unicode_literals
  2 
  3 import os
  4 import subprocess
  5 import sys
  6 
  7 from pre_commit import color
  8 from pre_commit import five
  9 
  10 
  11 # TODO: smell: import side-effects
  12 try:
  13     if not os.environ.get('TERM'):  # pragma: no cover (dumb terminal)
  14         raise OSError('Cannot determine width without TERM')
  15     COLS = int(
  16         subprocess.Popen(
  17             ('tput', 'cols'), stdout=subprocess.PIPE,
  18         ).communicate()[0] or
  19         # Default in the case of no terminal
  20         80
  21     )
  22 except OSError:  # pragma: no cover (windows)
  23     COLS = 80
  24 
  25 
  26 def get_hook_message(
  27         start,
  28         postfix='',
  29         end_msg=None,
  30         end_len=0,
  31         end_color=None,
  32         use_color=None,
  33         cols=COLS,
  34 ):
  35     """Prints a message for running a hook.
  36 
  37     This currently supports three approaches:
  38 
  39     # Print `start` followed by dots, leaving 6 characters at the end
  40     >>> print_hook_message('start', end_len=6)
  41     start...............................................................
  42 
  43     # Print `start` followed by dots with the end message colored if coloring
  44     # is specified and a newline afterwards
  45     >>> print_hook_message(
  46         'start',
  47         end_msg='end',
  48         end_color=color.RED,
  49         use_color=True,
  50     )
  51     start...................................................................end
  52 
  53     # Print `start` followed by dots, followed by the `postfix` message
  54     # uncolored, followed by the `end_msg` colored if specified and a newline
  55     # afterwards
  56     >>> print_hook_message(
  57         'start',
  58         postfix='postfix ',
  59         end_msg='end',
  60         end_color=color.RED,
  61         use_color=True,
  62     )
  63     start...........................................................postfix end
  64     """
  65     if bool(end_msg) == bool(end_len):
  66         raise ValueError('Expected one of (`end_msg`, `end_len`)')
  67     if end_msg is not None and (end_color is None or use_color is None):
  68         raise ValueError(
  69             '`end_color` and `use_color` are required with `end_msg`'
  70         )
  71 
  72     if end_len:
  73         return start + '.' * (cols - len(start) - end_len - 1)
  74     else:
  75         return '{0}{1}{2}{3}\n'.format(
  76             start,
  77             '.' * (cols - len(start) - len(postfix) - len(end_msg) - 1),
  78             postfix,
  79             color.format_color(end_msg, end_color, use_color),
  80         )
  81 
  82 
  83 stdout_byte_stream = getattr(sys.stdout, 'buffer', sys.stdout)
  84 
  85 
  86 def sys_stdout_write_wrapper(s, stream=stdout_byte_stream):
  87     stream.write(five.to_bytes(s))
  88
  ```
  
  Path: `pre_commit/languages/python.py`
  Content:
  ```
  1 from __future__ import unicode_literals
  2 
  3 import contextlib
  4 import distutils.spawn
  5 import os
  6 import sys
  7 
  8 import virtualenv
  9 
  10 from pre_commit.languages import helpers
  11 from pre_commit.util import clean_path_on_failure
  12 from pre_commit.util import shell_escape
  13 
  14 
  15 ENVIRONMENT_DIR = 'py_env'
  16 
  17 
  18 class PythonEnv(helpers.Environment):
  19     @property
  20     def env_prefix(self):
  21         return ". '{{prefix}}{0}activate' &&".format(
  22             virtualenv.path_locations(
  23                 helpers.environment_dir(ENVIRONMENT_DIR, self.language_version)
  24             )[-1].rstrip(os.sep) + os.sep,
  25         )
  26 
  27 
  28 @contextlib.contextmanager
  29 def in_env(repo_cmd_runner, language_version):
  30     yield PythonEnv(repo_cmd_runner, language_version)
  31 
  32 
  33 def norm_version(version):
  34     version = os.path.expanduser(version)
  35     if os.name == 'nt':  # pragma: no cover (windows)
  36         if not distutils.spawn.find_executable(version):
  37             # expanduser introduces a leading slash
  38             version = version.strip('\\')
  39             # The default place for python on windows is:
  40             # C:\PythonXX\python.exe
  41             version = r'C:\{0}\python.exe'.format(version.replace('.', ''))
  42     return version
  43 
  44 
  45 def install_environment(
  46         repo_cmd_runner,
  47         version='default',
  48         additional_dependencies=None,
  49 ):
  50     assert repo_cmd_runner.exists('setup.py')
  51     directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
  52 
  53     # Install a virtualenv
  54     with clean_path_on_failure(repo_cmd_runner.path(directory)):
  55         venv_cmd = [
  56             sys.executable, '-m', 'virtualenv',
  57             '{{prefix}}{0}'.format(directory)
  58         ]
  59         if version != 'default':
  60             venv_cmd.extend(['-p', norm_version(version)])
  61         repo_cmd_runner.run(venv_cmd)
  62         with in_env(repo_cmd_runner, version) as env:
  63             env.run("cd '{prefix}' && pip install .")
  64             if additional_dependencies:
  65                 env.run(
  66                     "cd '{prefix}' && pip install " +
  67                     ' '.join(
  68                         shell_escape(dep) for dep in additional_dependencies
  69                     )
  70                 )
  71 
  72 
  73 def run_hook(repo_cmd_runner, hook, file_args):
  74     with in_env(repo_cmd_runner, hook['language_version']) as env:
  75         return helpers.run_hook(env, hook, file_args)
  76
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py
  --- a/pre_commit/languages/python.py
  +++ b/pre_commit/languages/python.py
  @@ -31,15 +31,18 @@
   
   
   def norm_version(version):
  -    version = os.path.expanduser(version)
       if os.name == 'nt':  # pragma: no cover (windows)
  -        if not distutils.spawn.find_executable(version):
  -            # expanduser introduces a leading slash
  -            version = version.strip('\\')
  -            # The default place for python on windows is:
  -            # C:\PythonXX\python.exe
  -            version = r'C:\{0}\python.exe'.format(version.replace('.', ''))
  -    return version
  +        # Try looking up by name
  +        if distutils.spawn.find_executable(version):
  +            return version
  +
  +        # If it is in the form pythonx.x search in the default
  +        # place on windows
  +        if version.startswith('python'):
  +            return r'C:\{0}\python.exe'.format(version.replace('.', ''))
  +
  +        # Otherwise assume it is a path
  +    return os.path.expanduser(version)
   
   
   def install_environment(
  diff --git a/pre_commit/output.py b/pre_commit/output.py
  --- a/pre_commit/output.py
  +++ b/pre_commit/output.py
  @@ -12,13 +12,14 @@
   try:
       if not os.environ.get('TERM'):  # pragma: no cover (dumb terminal)
           raise OSError('Cannot determine width without TERM')
  -    COLS = int(
  -        subprocess.Popen(
  -            ('tput', 'cols'), stdout=subprocess.PIPE,
  -        ).communicate()[0] or
  -        # Default in the case of no terminal
  -        80
  -    )
  +    else:  # pragma no cover (windows)
  +        COLS = int(
  +            subprocess.Popen(
  +                ('tput', 'cols'), stdout=subprocess.PIPE,
  +            ).communicate()[0] or
  +            # Default in the case of no terminal
  +            80
  +        )
   except OSError:  # pragma: no cover (windows)
       COLS = 80
  
verification_info: {"golden_diff": "diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py\n--- a/pre_commit/languages/python.py\n+++ b/pre_commit/languages/python.py\n@@ -31,15 +31,18 @@\n \n \n def norm_version(version):\n-    version = os.path.expanduser(version)\n     if os.name == 'nt':  # pragma: no cover (windows)\n-        if not distutils.spawn.find_executable(version):\n-            # expanduser introduces a leading slash\n-            version = version.strip('\\\\')\n-            # The default place for python on windows is:\n-            # C:\\PythonXX\\python.exe\n-            version = r'C:\\{0}\\python.exe'.format(version.replace('.', ''))\n-    return version\n+        # Try looking up by name\n+        if distutils.spawn.find_executable(version):\n+            return version\n+\n+        # If it is in the form pythonx.x search in the default\n+        # place on windows\n+        if version.startswith('python'):\n+            return r'C:\\{0}\\python.exe'.format(version.replace('.', ''))\n+\n+        # Otherwise assume it is a path\n+    return os.path.expanduser(version)\n \n \n def install_environment(\ndiff --git a/pre_commit/output.py b/pre_commit/output.py\n--- a/pre_commit/output.py\n+++ b/pre_commit/output.py\n@@ -12,13 +12,14 @@\n try:\n     if not os.environ.get('TERM'):  # pragma: no cover (dumb terminal)\n         raise OSError('Cannot determine width without TERM')\n-    COLS = int(\n-        subprocess.Popen(\n-            ('tput', 'cols'), stdout=subprocess.PIPE,\n-        ).communicate()[0] or\n-        # Default in the case of no terminal\n-        80\n-    )\n+    else:  # pragma no cover (windows)\n+        COLS = int(\n+            subprocess.Popen(\n+                ('tput', 'cols'), stdout=subprocess.PIPE,\n+            ).communicate()[0] or\n+            # Default in the case of no terminal\n+            80\n+        )\n except OSError:  # pragma: no cover (windows)\n     COLS = 80\n", "issue": "Add option to pass additional dependencies to hooks\nI am currently working on implementing this framework and one of the things I am trying to run is eslint. As part of that I have a number of plugins that are in my configuration file. I think that, rather than forcing anyone who is using plugins to create a new hook definition with a corresponding package.json it might be useful to add a global option to pass a list of dependencies in the configuration file.\n\nFor instance, something lilke this:\n\n``` yaml\n-   repo: https://github.com/pre-commit/mirrors-eslint\n    sha: 135f285caf8e6e886b28c8e98fdff402b69c4490\n    hooks:\n    -   id: eslint\n        language_version: '0.12.7'\n        dependencies: [eslint-plugin-react, eslint-plugin-html]\n```\n\nand have those dependencies installed into the generated environment for that language.\n\nI am going to work on implementing this in my forked repo but would like feedback on whether this is a desired feature or any implementation advice on how best to facilitate this.\n\n", "before_files": [{"content": "from __future__ import unicode_literals\n\nimport os\nimport subprocess\nimport sys\n\nfrom pre_commit import color\nfrom pre_commit import five\n\n\n# TODO: smell: import side-effects\ntry:\n    if not os.environ.get('TERM'):  # pragma: no cover (dumb terminal)\n        raise OSError('Cannot determine width without TERM')\n    COLS = int(\n        subprocess.Popen(\n            ('tput', 'cols'), stdout=subprocess.PIPE,\n        ).communicate()[0] or\n        # Default in the case of no terminal\n        80\n    )\nexcept OSError:  # pragma: no cover (windows)\n    COLS = 80\n\n\ndef get_hook_message(\n        start,\n        postfix='',\n        end_msg=None,\n        end_len=0,\n        end_color=None,\n        use_color=None,\n        cols=COLS,\n):\n    \"\"\"Prints a message for running a hook.\n\n    This currently supports three approaches:\n\n    # Print `start` followed by dots, leaving 6 characters at the end\n    >>> print_hook_message('start', end_len=6)\n    start...............................................................\n\n    # Print `start` followed by dots with the end message colored if coloring\n    # is specified and a newline afterwards\n    >>> print_hook_message(\n        'start',\n        end_msg='end',\n        end_color=color.RED,\n        use_color=True,\n    )\n    start...................................................................end\n\n    # Print `start` followed by dots, followed by the `postfix` message\n    # uncolored, followed by the `end_msg` colored if specified and a newline\n    # afterwards\n    >>> print_hook_message(\n        'start',\n        postfix='postfix ',\n        end_msg='end',\n        end_color=color.RED,\n        use_color=True,\n    )\n    start...........................................................postfix end\n    \"\"\"\n    if bool(end_msg) == bool(end_len):\n        raise ValueError('Expected one of (`end_msg`, `end_len`)')\n    if end_msg is not None and (end_color is None or use_color is None):\n        raise ValueError(\n            '`end_color` and `use_color` are required with `end_msg`'\n        )\n\n    if end_len:\n        return start + '.' * (cols - len(start) - end_len - 1)\n    else:\n        return '{0}{1}{2}{3}\\n'.format(\n            start,\n            '.' * (cols - len(start) - len(postfix) - len(end_msg) - 1),\n            postfix,\n            color.format_color(end_msg, end_color, use_color),\n        )\n\n\nstdout_byte_stream = getattr(sys.stdout, 'buffer', sys.stdout)\n\n\ndef sys_stdout_write_wrapper(s, stream=stdout_byte_stream):\n    stream.write(five.to_bytes(s))\n", "path": "pre_commit/output.py"}, {"content": "from __future__ import unicode_literals\n\nimport contextlib\nimport distutils.spawn\nimport os\nimport sys\n\nimport virtualenv\n\nfrom pre_commit.languages import helpers\nfrom pre_commit.util import clean_path_on_failure\nfrom pre_commit.util import shell_escape\n\n\nENVIRONMENT_DIR = 'py_env'\n\n\nclass PythonEnv(helpers.Environment):\n    @property\n    def env_prefix(self):\n        return \". '{{prefix}}{0}activate' &&\".format(\n            virtualenv.path_locations(\n                helpers.environment_dir(ENVIRONMENT_DIR, self.language_version)\n            )[-1].rstrip(os.sep) + os.sep,\n        )\n\n\n@contextlib.contextmanager\ndef in_env(repo_cmd_runner, language_version):\n    yield PythonEnv(repo_cmd_runner, language_version)\n\n\ndef norm_version(version):\n    version = os.path.expanduser(version)\n    if os.name == 'nt':  # pragma: no cover (windows)\n        if not distutils.spawn.find_executable(version):\n            # expanduser introduces a leading slash\n            version = version.strip('\\\\')\n            # The default place for python on windows is:\n            # C:\\PythonXX\\python.exe\n            version = r'C:\\{0}\\python.exe'.format(version.replace('.', ''))\n    return version\n\n\ndef install_environment(\n        repo_cmd_runner,\n        version='default',\n        additional_dependencies=None,\n):\n    assert repo_cmd_runner.exists('setup.py')\n    directory = helpers.environment_dir(ENVIRONMENT_DIR, version)\n\n    # Install a virtualenv\n    with clean_path_on_failure(repo_cmd_runner.path(directory)):\n        venv_cmd = [\n            sys.executable, '-m', 'virtualenv',\n            '{{prefix}}{0}'.format(directory)\n        ]\n        if version != 'default':\n            venv_cmd.extend(['-p', norm_version(version)])\n        repo_cmd_runner.run(venv_cmd)\n        with in_env(repo_cmd_runner, version) as env:\n            env.run(\"cd '{prefix}' && pip install .\")\n            if additional_dependencies:\n                env.run(\n                    \"cd '{prefix}' && pip install \" +\n                    ' '.join(\n                        shell_escape(dep) for dep in additional_dependencies\n                    )\n                )\n\n\ndef run_hook(repo_cmd_runner, hook, file_args):\n    with in_env(repo_cmd_runner, hook['language_version']) as env:\n        return helpers.run_hook(env, hook, file_args)\n", "path": "pre_commit/languages/python.py"}], "after_files": [{"content": "from __future__ import unicode_literals\n\nimport os\nimport subprocess\nimport sys\n\nfrom pre_commit import color\nfrom pre_commit import five\n\n\n# TODO: smell: import side-effects\ntry:\n    if not os.environ.get('TERM'):  # pragma: no cover (dumb terminal)\n        raise OSError('Cannot determine width without TERM')\n    else:  # pragma no cover (windows)\n        COLS = int(\n            subprocess.Popen(\n                ('tput', 'cols'), stdout=subprocess.PIPE,\n            ).communicate()[0] or\n            # Default in the case of no terminal\n            80\n        )\nexcept OSError:  # pragma: no cover (windows)\n    COLS = 80\n\n\ndef get_hook_message(\n        start,\n        postfix='',\n        end_msg=None,\n        end_len=0,\n        end_color=None,\n        use_color=None,\n        cols=COLS,\n):\n    \"\"\"Prints a message for running a hook.\n\n    This currently supports three approaches:\n\n    # Print `start` followed by dots, leaving 6 characters at the end\n    >>> print_hook_message('start', end_len=6)\n    start...............................................................\n\n    # Print `start` followed by dots with the end message colored if coloring\n    # is specified and a newline afterwards\n    >>> print_hook_message(\n        'start',\n        end_msg='end',\n        end_color=color.RED,\n        use_color=True,\n    )\n    start...................................................................end\n\n    # Print `start` followed by dots, followed by the `postfix` message\n    # uncolored, followed by the `end_msg` colored if specified and a newline\n    # afterwards\n    >>> print_hook_message(\n        'start',\n        postfix='postfix ',\n        end_msg='end',\n        end_color=color.RED,\n        use_color=True,\n    )\n    start...........................................................postfix end\n    \"\"\"\n    if bool(end_msg) == bool(end_len):\n        raise ValueError('Expected one of (`end_msg`, `end_len`)')\n    if end_msg is not None and (end_color is None or use_color is None):\n        raise ValueError(\n            '`end_color` and `use_color` are required with `end_msg`'\n        )\n\n    if end_len:\n        return start + '.' * (cols - len(start) - end_len - 1)\n    else:\n        return '{0}{1}{2}{3}\\n'.format(\n            start,\n            '.' * (cols - len(start) - len(postfix) - len(end_msg) - 1),\n            postfix,\n            color.format_color(end_msg, end_color, use_color),\n        )\n\n\nstdout_byte_stream = getattr(sys.stdout, 'buffer', sys.stdout)\n\n\ndef sys_stdout_write_wrapper(s, stream=stdout_byte_stream):\n    stream.write(five.to_bytes(s))\n", "path": "pre_commit/output.py"}, {"content": "from __future__ import unicode_literals\n\nimport contextlib\nimport distutils.spawn\nimport os\nimport sys\n\nimport virtualenv\n\nfrom pre_commit.languages import helpers\nfrom pre_commit.util import clean_path_on_failure\nfrom pre_commit.util import shell_escape\n\n\nENVIRONMENT_DIR = 'py_env'\n\n\nclass PythonEnv(helpers.Environment):\n    @property\n    def env_prefix(self):\n        return \". '{{prefix}}{0}activate' &&\".format(\n            virtualenv.path_locations(\n                helpers.environment_dir(ENVIRONMENT_DIR, self.language_version)\n            )[-1].rstrip(os.sep) + os.sep,\n        )\n\n\n@contextlib.contextmanager\ndef in_env(repo_cmd_runner, language_version):\n    yield PythonEnv(repo_cmd_runner, language_version)\n\n\ndef norm_version(version):\n    if os.name == 'nt':  # pragma: no cover (windows)\n        # Try looking up by name\n        if distutils.spawn.find_executable(version):\n            return version\n\n        # If it is in the form pythonx.x search in the default\n        # place on windows\n        if version.startswith('python'):\n            return r'C:\\{0}\\python.exe'.format(version.replace('.', ''))\n\n        # Otherwise assume it is a path\n    return os.path.expanduser(version)\n\n\ndef install_environment(\n        repo_cmd_runner,\n        version='default',\n        additional_dependencies=None,\n):\n    assert repo_cmd_runner.exists('setup.py')\n    directory = helpers.environment_dir(ENVIRONMENT_DIR, version)\n\n    # Install a virtualenv\n    with clean_path_on_failure(repo_cmd_runner.path(directory)):\n        venv_cmd = [\n            sys.executable, '-m', 'virtualenv',\n            '{{prefix}}{0}'.format(directory)\n        ]\n        if version != 'default':\n            venv_cmd.extend(['-p', norm_version(version)])\n        repo_cmd_runner.run(venv_cmd)\n        with in_env(repo_cmd_runner, version) as env:\n            env.run(\"cd '{prefix}' && pip install .\")\n            if additional_dependencies:\n                env.run(\n                    \"cd '{prefix}' && pip install \" +\n                    ' '.join(\n                        shell_escape(dep) for dep in additional_dependencies\n                    )\n                )\n\n\ndef run_hook(repo_cmd_runner, hook, file_args):\n    with in_env(repo_cmd_runner, hook['language_version']) as env:\n        return helpers.run_hook(env, hook, file_args)\n", "path": "pre_commit/languages/python.py"}]}