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()

```

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

{"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"}]}