prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Simplify cookiecutter.hooks.find_hooks
  We should rename `cookiecutter.hooks.find_hooks` to `find_hook(hook_name)` and explicitly look for the requested hook, instead of processing all the files in the hooks directory.
  
  See https://github.com/audreyr/cookiecutter/pull/768/files/9a94484093ca23e9d55d42a53f096f67535b0b63#r68646614
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `cookiecutter/hooks.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 
  3 """Functions for discovering and executing various cookiecutter hooks."""
  4 
  5 import io
  6 import logging
  7 import os
  8 import subprocess
  9 import sys
  10 import tempfile
  11 
  12 from jinja2 import Template
  13 
  14 from cookiecutter import utils
  15 from .exceptions import FailedHookException
  16 
  17 logger = logging.getLogger(__name__)
  18 
  19 
  20 _HOOKS = [
  21     'pre_gen_project',
  22     'post_gen_project',
  23     # TODO: other hooks should be listed here
  24 ]
  25 EXIT_SUCCESS = 0
  26 
  27 
  28 def find_hooks():
  29     """Return a dict of all hook scripts provided.
  30 
  31     Must be called with the project template as the current working directory.
  32     Dict's key will be the hook/script's name, without extension, while values
  33     will be the absolute path to the script. Missing scripts will not be
  34     included in the returned dict.
  35     """
  36     hooks_dir = 'hooks'
  37     hooks = {}
  38     logger.debug('hooks_dir is {}'.format(hooks_dir))
  39 
  40     if not os.path.isdir(hooks_dir):
  41         logger.debug('No hooks/ dir in template_dir')
  42         return hooks
  43 
  44     for f in os.listdir(hooks_dir):
  45         filename = os.path.basename(f)
  46         basename = os.path.splitext(filename)[0]
  47 
  48         if basename in _HOOKS and not filename.endswith('~'):
  49             hooks[basename] = os.path.abspath(os.path.join(hooks_dir, f))
  50     return hooks
  51 
  52 
  53 def run_script(script_path, cwd='.'):
  54     """Execute a script from a working directory.
  55 
  56     :param script_path: Absolute path to the script to run.
  57     :param cwd: The directory to run the script from.
  58     """
  59     run_thru_shell = sys.platform.startswith('win')
  60     if script_path.endswith('.py'):
  61         script_command = [sys.executable, script_path]
  62     else:
  63         script_command = [script_path]
  64 
  65     utils.make_executable(script_path)
  66 
  67     proc = subprocess.Popen(
  68         script_command,
  69         shell=run_thru_shell,
  70         cwd=cwd
  71     )
  72     exit_status = proc.wait()
  73     if exit_status != EXIT_SUCCESS:
  74         raise FailedHookException(
  75             "Hook script failed (exit status: %d)" % exit_status)
  76 
  77 
  78 def run_script_with_context(script_path, cwd, context):
  79     """Execute a script after rendering it with Jinja.
  80 
  81     :param script_path: Absolute path to the script to run.
  82     :param cwd: The directory to run the script from.
  83     :param context: Cookiecutter project template context.
  84     """
  85     _, extension = os.path.splitext(script_path)
  86 
  87     contents = io.open(script_path, 'r', encoding='utf-8').read()
  88 
  89     with tempfile.NamedTemporaryFile(
  90         delete=False,
  91         mode='wb',
  92         suffix=extension
  93     ) as temp:
  94         output = Template(contents).render(**context)
  95         temp.write(output.encode('utf-8'))
  96 
  97     run_script(temp.name, cwd)
  98 
  99 
  100 def run_hook(hook_name, project_dir, context):
  101     """
  102     Try to find and execute a hook from the specified project directory.
  103 
  104     :param hook_name: The hook to execute.
  105     :param project_dir: The directory to execute the script from.
  106     :param context: Cookiecutter project context.
  107     """
  108     script = find_hooks().get(hook_name)
  109     if script is None:
  110         logger.debug('No hooks found')
  111         return
  112     logger.debug('Running hook {}'.format(hook_name))
  113     run_script_with_context(script, project_dir, context)
  114
  ```
  --- 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/cookiecutter/hooks.py b/cookiecutter/hooks.py
  --- a/cookiecutter/hooks.py
  +++ b/cookiecutter/hooks.py
  @@ -16,38 +16,53 @@
   
   logger = logging.getLogger(__name__)
   
  -
   _HOOKS = [
       'pre_gen_project',
       'post_gen_project',
  -    # TODO: other hooks should be listed here
   ]
   EXIT_SUCCESS = 0
   
   
  -def find_hooks():
  +def valid_hook(hook_file, hook_name):
  +    """Determine if a hook file is valid.
  +
  +    :param hook_file: The hook file to consider for validity
  +    :param hook_name: The hook to find
  +    :return: The hook file validity
  +    """
  +    filename = os.path.basename(hook_file)
  +    basename = os.path.splitext(filename)[0]
  +
  +    matching_hook = basename == hook_name
  +    supported_hook = basename in _HOOKS
  +    backup_file = filename.endswith('~')
  +
  +    return matching_hook and supported_hook and not backup_file
  +
  +
  +def find_hook(hook_name, hooks_dir='hooks'):
       """Return a dict of all hook scripts provided.
   
       Must be called with the project template as the current working directory.
       Dict's key will be the hook/script's name, without extension, while values
       will be the absolute path to the script. Missing scripts will not be
       included in the returned dict.
  +
  +    :param hook_name: The hook to find
  +    :param hooks_dir: The hook directory in the template
  +    :return: The absolute path to the hook script or None
       """
  -    hooks_dir = 'hooks'
  -    hooks = {}
  -    logger.debug('hooks_dir is {}'.format(hooks_dir))
  +    logger.debug('hooks_dir is {}'.format(os.path.abspath(hooks_dir)))
   
       if not os.path.isdir(hooks_dir):
           logger.debug('No hooks/ dir in template_dir')
  -        return hooks
  +        return None
   
  -    for f in os.listdir(hooks_dir):
  -        filename = os.path.basename(f)
  -        basename = os.path.splitext(filename)[0]
  +    for hook_file in os.listdir(hooks_dir):
  +        if valid_hook(hook_file, hook_name):
  +            return os.path.abspath(os.path.join(hooks_dir, hook_file))
   
  -        if basename in _HOOKS and not filename.endswith('~'):
  -            hooks[basename] = os.path.abspath(os.path.join(hooks_dir, f))
  -    return hooks
  +    return None
   
   
   def run_script(script_path, cwd='.'):
  @@ -105,7 +120,7 @@
       :param project_dir: The directory to execute the script from.
       :param context: Cookiecutter project context.
       """
  -    script = find_hooks().get(hook_name)
  +    script = find_hook(hook_name)
       if script is None:
           logger.debug('No hooks found')
           return
  
verification_info: {"golden_diff": "diff --git a/cookiecutter/hooks.py b/cookiecutter/hooks.py\n--- a/cookiecutter/hooks.py\n+++ b/cookiecutter/hooks.py\n@@ -16,38 +16,53 @@\n \n logger = logging.getLogger(__name__)\n \n-\n _HOOKS = [\n     'pre_gen_project',\n     'post_gen_project',\n-    # TODO: other hooks should be listed here\n ]\n EXIT_SUCCESS = 0\n \n \n-def find_hooks():\n+def valid_hook(hook_file, hook_name):\n+    \"\"\"Determine if a hook file is valid.\n+\n+    :param hook_file: The hook file to consider for validity\n+    :param hook_name: The hook to find\n+    :return: The hook file validity\n+    \"\"\"\n+    filename = os.path.basename(hook_file)\n+    basename = os.path.splitext(filename)[0]\n+\n+    matching_hook = basename == hook_name\n+    supported_hook = basename in _HOOKS\n+    backup_file = filename.endswith('~')\n+\n+    return matching_hook and supported_hook and not backup_file\n+\n+\n+def find_hook(hook_name, hooks_dir='hooks'):\n     \"\"\"Return a dict of all hook scripts provided.\n \n     Must be called with the project template as the current working directory.\n     Dict's key will be the hook/script's name, without extension, while values\n     will be the absolute path to the script. Missing scripts will not be\n     included in the returned dict.\n+\n+    :param hook_name: The hook to find\n+    :param hooks_dir: The hook directory in the template\n+    :return: The absolute path to the hook script or None\n     \"\"\"\n-    hooks_dir = 'hooks'\n-    hooks = {}\n-    logger.debug('hooks_dir is {}'.format(hooks_dir))\n+    logger.debug('hooks_dir is {}'.format(os.path.abspath(hooks_dir)))\n \n     if not os.path.isdir(hooks_dir):\n         logger.debug('No hooks/ dir in template_dir')\n-        return hooks\n+        return None\n \n-    for f in os.listdir(hooks_dir):\n-        filename = os.path.basename(f)\n-        basename = os.path.splitext(filename)[0]\n+    for hook_file in os.listdir(hooks_dir):\n+        if valid_hook(hook_file, hook_name):\n+            return os.path.abspath(os.path.join(hooks_dir, hook_file))\n \n-        if basename in _HOOKS and not filename.endswith('~'):\n-            hooks[basename] = os.path.abspath(os.path.join(hooks_dir, f))\n-    return hooks\n+    return None\n \n \n def run_script(script_path, cwd='.'):\n@@ -105,7 +120,7 @@\n     :param project_dir: The directory to execute the script from.\n     :param context: Cookiecutter project context.\n     \"\"\"\n-    script = find_hooks().get(hook_name)\n+    script = find_hook(hook_name)\n     if script is None:\n         logger.debug('No hooks found')\n         return\n", "issue": "Simplify cookiecutter.hooks.find_hooks\nWe should rename `cookiecutter.hooks.find_hooks` to `find_hook(hook_name)` and explicitly look for the requested hook, instead of processing all the files in the hooks directory.\n\nSee https://github.com/audreyr/cookiecutter/pull/768/files/9a94484093ca23e9d55d42a53f096f67535b0b63#r68646614\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"Functions for discovering and executing various cookiecutter hooks.\"\"\"\n\nimport io\nimport logging\nimport os\nimport subprocess\nimport sys\nimport tempfile\n\nfrom jinja2 import Template\n\nfrom cookiecutter import utils\nfrom .exceptions import FailedHookException\n\nlogger = logging.getLogger(__name__)\n\n\n_HOOKS = [\n    'pre_gen_project',\n    'post_gen_project',\n    # TODO: other hooks should be listed here\n]\nEXIT_SUCCESS = 0\n\n\ndef find_hooks():\n    \"\"\"Return a dict of all hook scripts provided.\n\n    Must be called with the project template as the current working directory.\n    Dict's key will be the hook/script's name, without extension, while values\n    will be the absolute path to the script. Missing scripts will not be\n    included in the returned dict.\n    \"\"\"\n    hooks_dir = 'hooks'\n    hooks = {}\n    logger.debug('hooks_dir is {}'.format(hooks_dir))\n\n    if not os.path.isdir(hooks_dir):\n        logger.debug('No hooks/ dir in template_dir')\n        return hooks\n\n    for f in os.listdir(hooks_dir):\n        filename = os.path.basename(f)\n        basename = os.path.splitext(filename)[0]\n\n        if basename in _HOOKS and not filename.endswith('~'):\n            hooks[basename] = os.path.abspath(os.path.join(hooks_dir, f))\n    return hooks\n\n\ndef run_script(script_path, cwd='.'):\n    \"\"\"Execute a script from a working directory.\n\n    :param script_path: Absolute path to the script to run.\n    :param cwd: The directory to run the script from.\n    \"\"\"\n    run_thru_shell = sys.platform.startswith('win')\n    if script_path.endswith('.py'):\n        script_command = [sys.executable, script_path]\n    else:\n        script_command = [script_path]\n\n    utils.make_executable(script_path)\n\n    proc = subprocess.Popen(\n        script_command,\n        shell=run_thru_shell,\n        cwd=cwd\n    )\n    exit_status = proc.wait()\n    if exit_status != EXIT_SUCCESS:\n        raise FailedHookException(\n            \"Hook script failed (exit status: %d)\" % exit_status)\n\n\ndef run_script_with_context(script_path, cwd, context):\n    \"\"\"Execute a script after rendering it with Jinja.\n\n    :param script_path: Absolute path to the script to run.\n    :param cwd: The directory to run the script from.\n    :param context: Cookiecutter project template context.\n    \"\"\"\n    _, extension = os.path.splitext(script_path)\n\n    contents = io.open(script_path, 'r', encoding='utf-8').read()\n\n    with tempfile.NamedTemporaryFile(\n        delete=False,\n        mode='wb',\n        suffix=extension\n    ) as temp:\n        output = Template(contents).render(**context)\n        temp.write(output.encode('utf-8'))\n\n    run_script(temp.name, cwd)\n\n\ndef run_hook(hook_name, project_dir, context):\n    \"\"\"\n    Try to find and execute a hook from the specified project directory.\n\n    :param hook_name: The hook to execute.\n    :param project_dir: The directory to execute the script from.\n    :param context: Cookiecutter project context.\n    \"\"\"\n    script = find_hooks().get(hook_name)\n    if script is None:\n        logger.debug('No hooks found')\n        return\n    logger.debug('Running hook {}'.format(hook_name))\n    run_script_with_context(script, project_dir, context)\n", "path": "cookiecutter/hooks.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"Functions for discovering and executing various cookiecutter hooks.\"\"\"\n\nimport io\nimport logging\nimport os\nimport subprocess\nimport sys\nimport tempfile\n\nfrom jinja2 import Template\n\nfrom cookiecutter import utils\nfrom .exceptions import FailedHookException\n\nlogger = logging.getLogger(__name__)\n\n_HOOKS = [\n    'pre_gen_project',\n    'post_gen_project',\n]\nEXIT_SUCCESS = 0\n\n\ndef valid_hook(hook_file, hook_name):\n    \"\"\"Determine if a hook file is valid.\n\n    :param hook_file: The hook file to consider for validity\n    :param hook_name: The hook to find\n    :return: The hook file validity\n    \"\"\"\n    filename = os.path.basename(hook_file)\n    basename = os.path.splitext(filename)[0]\n\n    matching_hook = basename == hook_name\n    supported_hook = basename in _HOOKS\n    backup_file = filename.endswith('~')\n\n    return matching_hook and supported_hook and not backup_file\n\n\ndef find_hook(hook_name, hooks_dir='hooks'):\n    \"\"\"Return a dict of all hook scripts provided.\n\n    Must be called with the project template as the current working directory.\n    Dict's key will be the hook/script's name, without extension, while values\n    will be the absolute path to the script. Missing scripts will not be\n    included in the returned dict.\n\n    :param hook_name: The hook to find\n    :param hooks_dir: The hook directory in the template\n    :return: The absolute path to the hook script or None\n    \"\"\"\n    logger.debug('hooks_dir is {}'.format(os.path.abspath(hooks_dir)))\n\n    if not os.path.isdir(hooks_dir):\n        logger.debug('No hooks/ dir in template_dir')\n        return None\n\n    for hook_file in os.listdir(hooks_dir):\n        if valid_hook(hook_file, hook_name):\n            return os.path.abspath(os.path.join(hooks_dir, hook_file))\n\n    return None\n\n\ndef run_script(script_path, cwd='.'):\n    \"\"\"Execute a script from a working directory.\n\n    :param script_path: Absolute path to the script to run.\n    :param cwd: The directory to run the script from.\n    \"\"\"\n    run_thru_shell = sys.platform.startswith('win')\n    if script_path.endswith('.py'):\n        script_command = [sys.executable, script_path]\n    else:\n        script_command = [script_path]\n\n    utils.make_executable(script_path)\n\n    proc = subprocess.Popen(\n        script_command,\n        shell=run_thru_shell,\n        cwd=cwd\n    )\n    exit_status = proc.wait()\n    if exit_status != EXIT_SUCCESS:\n        raise FailedHookException(\n            \"Hook script failed (exit status: %d)\" % exit_status)\n\n\ndef run_script_with_context(script_path, cwd, context):\n    \"\"\"Execute a script after rendering it with Jinja.\n\n    :param script_path: Absolute path to the script to run.\n    :param cwd: The directory to run the script from.\n    :param context: Cookiecutter project template context.\n    \"\"\"\n    _, extension = os.path.splitext(script_path)\n\n    contents = io.open(script_path, 'r', encoding='utf-8').read()\n\n    with tempfile.NamedTemporaryFile(\n        delete=False,\n        mode='wb',\n        suffix=extension\n    ) as temp:\n        output = Template(contents).render(**context)\n        temp.write(output.encode('utf-8'))\n\n    run_script(temp.name, cwd)\n\n\ndef run_hook(hook_name, project_dir, context):\n    \"\"\"\n    Try to find and execute a hook from the specified project directory.\n\n    :param hook_name: The hook to execute.\n    :param project_dir: The directory to execute the script from.\n    :param context: Cookiecutter project context.\n    \"\"\"\n    script = find_hook(hook_name)\n    if script is None:\n        logger.debug('No hooks found')\n        return\n    logger.debug('Running hook {}'.format(hook_name))\n    run_script_with_context(script, project_dir, context)\n", "path": "cookiecutter/hooks.py"}]}