Repository: pre-commit/pre-commit

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.



Discussion:
Sounds pretty reasonable, how about `additional_dependencies` instead of `dependencies`?

This would actually be a nice way to replace the wacky mirrors repos!

Yeah, that's reasonable. I'll update. I'm currently working on fixing broken tests and adding some to cover the new functionality.

Sounds great :+1: 

Via #295 


Resolving patch:
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 in_env(repo_cmd_runner, language_version):
 
 
 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
 


Test patch:
diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py
--- a/tests/commands/install_uninstall_test.py
+++ b/tests/commands/install_uninstall_test.py
@@ -90,7 +90,9 @@ def test_install_hooks_directory_not_present(tempdir_factory):
 
 
 @xfailif_no_symlink
-def test_install_hooks_dead_symlink(tempdir_factory):
+def test_install_hooks_dead_symlink(
+        tempdir_factory,
+):  # pragma: no cover (non-windows)
     path = git_dir(tempdir_factory)
     os.symlink('/fake/baz', os.path.join(path, '.git', 'hooks', 'pre-commit'))
     runner = Runner(path)
@@ -175,6 +177,14 @@ def test_install_idempotent(tempdir_factory):
         assert NORMAL_PRE_COMMIT_RUN.match(output)
 
 
+def _path_without_us():
+    # Choose a path which *probably* doesn't include us
+    return os.pathsep.join([
+        x for x in os.environ['PATH'].split(os.pathsep)
+        if x.lower() != os.path.dirname(sys.executable).lower()
+    ])
+
+
 def test_environment_not_sourced(tempdir_factory):
     path = make_consuming_repo(tempdir_factory, 'script_hooks_repo')
     with cwd(path):
@@ -193,7 +203,7 @@ def test_environment_not_sourced(tempdir_factory):
             )
         ret, stdout, stderr = cmd_output(
             'git', 'commit', '--allow-empty', '-m', 'foo',
-            env={'HOME': homedir},
+            env={'HOME': homedir, 'PATH': _path_without_us()},
             retcode=None,
         )
         assert ret == 1
@@ -422,6 +432,7 @@ def test_installed_from_venv(tempdir_factory):
             tempdir_factory,
             env_base={
                 'HOME': os.path.expanduser('~'),
+                'PATH': _path_without_us(),
                 'TERM': os.environ.get('TERM', ''),
                 # Windows needs this to import `random`
                 'SYSTEMROOT': os.environ.get('SYSTEMROOT', ''),
diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py
new file mode 100644
--- /dev/null
+++ b/tests/languages/python_test.py
@@ -0,0 +1,18 @@
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import os.path
+
+from pre_commit.languages import python
+
+
+def test_norm_version_expanduser():
+    home = os.path.expanduser('~')
+    if os.name == 'nt':  # pragma: no cover (nt)
+        path = r'~\python343'
+        expected_path = r'{0}\python343'.format(home)
+    else:  # pragma: no cover (non-nt)
+        path = '~/.pyenv/versions/3.4.3/bin/python'
+        expected_path = home + '/.pyenv/versions/3.4.3/bin/python'
+    result = python.norm_version(path)
+    assert result == expected_path
diff --git a/tests/repository_test.py b/tests/repository_test.py
--- a/tests/repository_test.py
+++ b/tests/repository_test.py
@@ -342,7 +342,9 @@ def test_additional_python_dependencies_installed(tempdir_factory, store):
 
 @xfailif_windows_no_ruby
 @pytest.mark.integration
-def test_additional_ruby_dependencies_installed(tempdir_factory, store):
+def test_additional_ruby_dependencies_installed(
+        tempdir_factory, store,
+):  # pragma: no cover (non-windows)
     path = make_repo(tempdir_factory, 'ruby_hooks_repo')
     config = make_config_from_repo(path)
     config['hooks'][0]['additional_dependencies'] = ['thread_safe']
@@ -355,7 +357,9 @@ def test_additional_ruby_dependencies_installed(tempdir_factory, store):
 
 @xfailif_windows_no_node
 @pytest.mark.integration
-def test_additional_node_dependencies_installed(tempdir_factory, store):
+def test_additional_node_dependencies_installed(
+        tempdir_factory, store,
+):  # pragma: no cover (non-windows)
     path = make_repo(tempdir_factory, 'node_hooks_repo')
     config = make_config_from_repo(path)
     # Careful to choose a small package that's not depped by npm
@@ -481,15 +485,3 @@ def test_local_repository():
     with pytest.raises(NotImplementedError):
         local_repo.manifest
     assert len(local_repo.hooks) == 1
-
-
-def test_norm_version_expanduser():  # pragma: no cover
-    home = os.path.expanduser('~')
-    if os.name == 'nt':
-        path = r'~\python343'
-        expected_path = r'C:{0}\python343\python.exe'.format(home)
-    else:
-        path = '~/.pyenv/versions/3.4.3/bin/python'
-        expected_path = home + '/.pyenv/versions/3.4.3/bin/python'
-    result = python.norm_version(path)
-    assert result == expected_path