prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  documention regarding adding new python based hooks needs improvement
  Apparently we need some kind of how-to or mini tutorial on how to add a new hook to pre-commit as the basic documentation does not help someone without previous pre-commit knowledge.
  
  I wanted to add support for `bashate`, a shell script linter written in python, available on pypi and that installs a shell script with the same name that can be used just like other linters.
  
  Initially I went to https://pre-commit.com/#new-hooks which didn't give me enough info. So, i looked for other linters based on python and I found yamllint, which pointed me to https://github.com/adrienverge/yamllint/blob/master/.pre-commit-hooks.yaml
  
  So the idea was to add the hook definition directly to the linter package. In this case I had to fork bashate in order to test the new, hook. So I ended up creating https://github.com/ssbarnea/bashate/blob/master/.pre-commit-hooks.yaml -- folowing the same model used in yamllint.
  
  Now, I wanted to add and test the hook on one of the repos I maintain so I did create https://github.com/pycontribs/jira/blob/feature/pre-commit/.pre-commit-config.yaml#L25
  
  When I tried to run it using `pre-commit run bashate --all`, it failed with this error:
  ```
  Bashate..................................................................An unexpected error has occurred: OSError: [Errno 2] No such file or directory
  Check the log at /Users/ssbarnea/.cache/pre-commit/pre-commit.log
  
  An unexpected error has occurred: OSError: [Errno 2] No such file or directory
  Traceback (most recent call last):
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/error_handler.py", line 47, in error_handler
      yield
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/main.py", line 258, in main
      return run(runner, args)
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/commands/run.py", line 270, in run
      return _run_hooks(runner.config, repo_hooks, args, environ)
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/commands/run.py", line 199, in _run_hooks
      retval |= _run_single_hook(filenames, hook, repo, args, skips, cols)
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/commands/run.py", line 110, in _run_single_hook
      hook, tuple(filenames) if hook['pass_filenames'] else (),
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/repository.py", line 207, in run_hook
      return languages[language_name].run_hook(prefix, hook, file_args)
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/languages/script.py", line 16, in run_hook
      return xargs(cmd, file_args)
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/xargs.py", line 63, in xargs
      *run_cmd, encoding=None, retcode=None
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/util.py", line 167, in cmd_output
      proc = subprocess.Popen(cmd, **popen_kwargs)
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/subprocess.py", line 390, in __init__
      errread, errwrite)
    File "/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/subprocess.py", line 1025, in _execute_child
      raise child_exception
  OSError: [Errno 2] No such file or directory
  ```
  
  At the moment I have no idea what I did wrong, probably something simple. Still, we need to make it easier to integrate new linters into pre-commit.
  
  --- 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/parse_shebang.py`
  Content:
  ```
  1 from __future__ import absolute_import
  2 from __future__ import unicode_literals
  3 
  4 import os.path
  5 
  6 from identify.identify import parse_shebang_from_file
  7 
  8 
  9 class ExecutableNotFoundError(OSError):
  10     def to_output(self):
  11         return (1, self.args[0].encode('UTF-8'), b'')
  12 
  13 
  14 def parse_filename(filename):
  15     if not os.path.exists(filename):
  16         return ()
  17     else:
  18         return parse_shebang_from_file(filename)
  19 
  20 
  21 def find_executable(exe, _environ=None):
  22     exe = os.path.normpath(exe)
  23     if os.sep in exe:
  24         return exe
  25 
  26     environ = _environ if _environ is not None else os.environ
  27 
  28     if 'PATHEXT' in environ:
  29         possible_exe_names = tuple(
  30             exe + ext.lower() for ext in environ['PATHEXT'].split(os.pathsep)
  31         ) + (exe,)
  32 
  33     else:
  34         possible_exe_names = (exe,)
  35 
  36     for path in environ.get('PATH', '').split(os.pathsep):
  37         for possible_exe_name in possible_exe_names:
  38             joined = os.path.join(path, possible_exe_name)
  39             if os.path.isfile(joined) and os.access(joined, os.X_OK):
  40                 return joined
  41     else:
  42         return None
  43 
  44 
  45 def normexe(orig_exe):
  46     if os.sep not in orig_exe:
  47         exe = find_executable(orig_exe)
  48         if exe is None:
  49             raise ExecutableNotFoundError(
  50                 'Executable `{}` not found'.format(orig_exe),
  51             )
  52         return exe
  53     else:
  54         return orig_exe
  55 
  56 
  57 def normalize_cmd(cmd):
  58     """Fixes for the following issues on windows
  59     - https://bugs.python.org/issue8557
  60     - windows does not parse shebangs
  61 
  62     This function also makes deep-path shebangs work just fine
  63     """
  64     # Use PATH to determine the executable
  65     exe = normexe(cmd[0])
  66 
  67     # Figure out the shebang from the resulting command
  68     cmd = parse_filename(exe) + (exe,) + cmd[1:]
  69 
  70     # This could have given us back another bare executable
  71     exe = normexe(cmd[0])
  72 
  73     return (exe,) + cmd[1:]
  74
  ```
  --- 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/parse_shebang.py b/pre_commit/parse_shebang.py
  --- a/pre_commit/parse_shebang.py
  +++ b/pre_commit/parse_shebang.py
  @@ -42,16 +42,21 @@
           return None
   
   
  -def normexe(orig_exe):
  -    if os.sep not in orig_exe:
  -        exe = find_executable(orig_exe)
  +def normexe(orig):
  +    def _error(msg):
  +        raise ExecutableNotFoundError('Executable `{}` {}'.format(orig, msg))
  +
  +    if os.sep not in orig and (not os.altsep or os.altsep not in orig):
  +        exe = find_executable(orig)
           if exe is None:
  -            raise ExecutableNotFoundError(
  -                'Executable `{}` not found'.format(orig_exe),
  -            )
  +            _error('not found')
           return exe
  +    elif not os.access(orig, os.X_OK):
  +        _error('not found')
  +    elif os.path.isdir(orig):
  +        _error('is a directory')
       else:
  -        return orig_exe
  +        return orig
   
   
   def normalize_cmd(cmd):
  
verification_info: {"golden_diff": "diff --git a/pre_commit/parse_shebang.py b/pre_commit/parse_shebang.py\n--- a/pre_commit/parse_shebang.py\n+++ b/pre_commit/parse_shebang.py\n@@ -42,16 +42,21 @@\n         return None\n \n \n-def normexe(orig_exe):\n-    if os.sep not in orig_exe:\n-        exe = find_executable(orig_exe)\n+def normexe(orig):\n+    def _error(msg):\n+        raise ExecutableNotFoundError('Executable `{}` {}'.format(orig, msg))\n+\n+    if os.sep not in orig and (not os.altsep or os.altsep not in orig):\n+        exe = find_executable(orig)\n         if exe is None:\n-            raise ExecutableNotFoundError(\n-                'Executable `{}` not found'.format(orig_exe),\n-            )\n+            _error('not found')\n         return exe\n+    elif not os.access(orig, os.X_OK):\n+        _error('not found')\n+    elif os.path.isdir(orig):\n+        _error('is a directory')\n     else:\n-        return orig_exe\n+        return orig\n \n \n def normalize_cmd(cmd):\n", "issue": "documention regarding adding new python based hooks needs improvement\nApparently we need some kind of how-to or mini tutorial on how to add a new hook to pre-commit as the basic documentation does not help someone without previous pre-commit knowledge.\r\n\r\nI wanted to add support for `bashate`, a shell script linter written in python, available on pypi and that installs a shell script with the same name that can be used just like other linters.\r\n\r\nInitially I went to https://pre-commit.com/#new-hooks which didn't give me enough info. So, i looked for other linters based on python and I found yamllint, which pointed me to https://github.com/adrienverge/yamllint/blob/master/.pre-commit-hooks.yaml\r\n\r\nSo the idea was to add the hook definition directly to the linter package. In this case I had to fork bashate in order to test the new, hook. So I ended up creating https://github.com/ssbarnea/bashate/blob/master/.pre-commit-hooks.yaml -- folowing the same model used in yamllint.\r\n\r\nNow, I wanted to add and test the hook on one of the repos I maintain so I did create https://github.com/pycontribs/jira/blob/feature/pre-commit/.pre-commit-config.yaml#L25\r\n\r\nWhen I tried to run it using `pre-commit run bashate --all`, it failed with this error:\r\n```\r\nBashate..................................................................An unexpected error has occurred: OSError: [Errno 2] No such file or directory\r\nCheck the log at /Users/ssbarnea/.cache/pre-commit/pre-commit.log\r\n\r\nAn unexpected error has occurred: OSError: [Errno 2] No such file or directory\r\nTraceback (most recent call last):\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/error_handler.py\", line 47, in error_handler\r\n    yield\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/main.py\", line 258, in main\r\n    return run(runner, args)\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/commands/run.py\", line 270, in run\r\n    return _run_hooks(runner.config, repo_hooks, args, environ)\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/commands/run.py\", line 199, in _run_hooks\r\n    retval |= _run_single_hook(filenames, hook, repo, args, skips, cols)\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/commands/run.py\", line 110, in _run_single_hook\r\n    hook, tuple(filenames) if hook['pass_filenames'] else (),\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/repository.py\", line 207, in run_hook\r\n    return languages[language_name].run_hook(prefix, hook, file_args)\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/languages/script.py\", line 16, in run_hook\r\n    return xargs(cmd, file_args)\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/xargs.py\", line 63, in xargs\r\n    *run_cmd, encoding=None, retcode=None\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/site-packages/pre_commit/util.py\", line 167, in cmd_output\r\n    proc = subprocess.Popen(cmd, **popen_kwargs)\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/subprocess.py\", line 390, in __init__\r\n    errread, errwrite)\r\n  File \"/Users/ssbarnea/.pyenv/versions/2.7.14/lib/python2.7/subprocess.py\", line 1025, in _execute_child\r\n    raise child_exception\r\nOSError: [Errno 2] No such file or directory\r\n```\r\n\r\nAt the moment I have no idea what I did wrong, probably something simple. Still, we need to make it easier to integrate new linters into pre-commit.\n", "before_files": [{"content": "from __future__ import absolute_import\nfrom __future__ import unicode_literals\n\nimport os.path\n\nfrom identify.identify import parse_shebang_from_file\n\n\nclass ExecutableNotFoundError(OSError):\n    def to_output(self):\n        return (1, self.args[0].encode('UTF-8'), b'')\n\n\ndef parse_filename(filename):\n    if not os.path.exists(filename):\n        return ()\n    else:\n        return parse_shebang_from_file(filename)\n\n\ndef find_executable(exe, _environ=None):\n    exe = os.path.normpath(exe)\n    if os.sep in exe:\n        return exe\n\n    environ = _environ if _environ is not None else os.environ\n\n    if 'PATHEXT' in environ:\n        possible_exe_names = tuple(\n            exe + ext.lower() for ext in environ['PATHEXT'].split(os.pathsep)\n        ) + (exe,)\n\n    else:\n        possible_exe_names = (exe,)\n\n    for path in environ.get('PATH', '').split(os.pathsep):\n        for possible_exe_name in possible_exe_names:\n            joined = os.path.join(path, possible_exe_name)\n            if os.path.isfile(joined) and os.access(joined, os.X_OK):\n                return joined\n    else:\n        return None\n\n\ndef normexe(orig_exe):\n    if os.sep not in orig_exe:\n        exe = find_executable(orig_exe)\n        if exe is None:\n            raise ExecutableNotFoundError(\n                'Executable `{}` not found'.format(orig_exe),\n            )\n        return exe\n    else:\n        return orig_exe\n\n\ndef normalize_cmd(cmd):\n    \"\"\"Fixes for the following issues on windows\n    - https://bugs.python.org/issue8557\n    - windows does not parse shebangs\n\n    This function also makes deep-path shebangs work just fine\n    \"\"\"\n    # Use PATH to determine the executable\n    exe = normexe(cmd[0])\n\n    # Figure out the shebang from the resulting command\n    cmd = parse_filename(exe) + (exe,) + cmd[1:]\n\n    # This could have given us back another bare executable\n    exe = normexe(cmd[0])\n\n    return (exe,) + cmd[1:]\n", "path": "pre_commit/parse_shebang.py"}], "after_files": [{"content": "from __future__ import absolute_import\nfrom __future__ import unicode_literals\n\nimport os.path\n\nfrom identify.identify import parse_shebang_from_file\n\n\nclass ExecutableNotFoundError(OSError):\n    def to_output(self):\n        return (1, self.args[0].encode('UTF-8'), b'')\n\n\ndef parse_filename(filename):\n    if not os.path.exists(filename):\n        return ()\n    else:\n        return parse_shebang_from_file(filename)\n\n\ndef find_executable(exe, _environ=None):\n    exe = os.path.normpath(exe)\n    if os.sep in exe:\n        return exe\n\n    environ = _environ if _environ is not None else os.environ\n\n    if 'PATHEXT' in environ:\n        possible_exe_names = tuple(\n            exe + ext.lower() for ext in environ['PATHEXT'].split(os.pathsep)\n        ) + (exe,)\n\n    else:\n        possible_exe_names = (exe,)\n\n    for path in environ.get('PATH', '').split(os.pathsep):\n        for possible_exe_name in possible_exe_names:\n            joined = os.path.join(path, possible_exe_name)\n            if os.path.isfile(joined) and os.access(joined, os.X_OK):\n                return joined\n    else:\n        return None\n\n\ndef normexe(orig):\n    def _error(msg):\n        raise ExecutableNotFoundError('Executable `{}` {}'.format(orig, msg))\n\n    if os.sep not in orig and (not os.altsep or os.altsep not in orig):\n        exe = find_executable(orig)\n        if exe is None:\n            _error('not found')\n        return exe\n    elif not os.access(orig, os.X_OK):\n        _error('not found')\n    elif os.path.isdir(orig):\n        _error('is a directory')\n    else:\n        return orig\n\n\ndef normalize_cmd(cmd):\n    \"\"\"Fixes for the following issues on windows\n    - https://bugs.python.org/issue8557\n    - windows does not parse shebangs\n\n    This function also makes deep-path shebangs work just fine\n    \"\"\"\n    # Use PATH to determine the executable\n    exe = normexe(cmd[0])\n\n    # Figure out the shebang from the resulting command\n    cmd = parse_filename(exe) + (exe,) + cmd[1:]\n\n    # This could have given us back another bare executable\n    exe = normexe(cmd[0])\n\n    return (exe,) + cmd[1:]\n", "path": "pre_commit/parse_shebang.py"}]}