Completed
Pull Request — master (#834)
by Michael
01:11
created

find_hook()   B

Complexity

Conditions 6

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
dl 0
loc 30
rs 7.5384
c 0
b 0
f 0
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
_HOOKS = [
20
    'pre_gen_project',
21
    'post_gen_project',
22
]
23
EXIT_SUCCESS = 0
24
25
26
def find_hook(hook_name, hooks_dir='hooks'):
27
    """Return a dict of all hook scripts provided.
28
29
    Must be called with the project template as the current working directory.
30
    Dict's key will be the hook/script's name, without extension, while values
31
    will be the absolute path to the script. Missing scripts will not be
32
    included in the returned dict.
33
34
    :param hook_name: The hook to find
35
    :param hooks_dir: The hook directory in the template
36
    :return: The absolute path to the hook script or None
37
    """
38
    logger.debug('hooks_dir is {}'.format(os.path.abspath(hooks_dir)))
39
40
    if not os.path.isdir(hooks_dir):
41
        logger.debug('No hooks/ dir in template_dir')
42
        return None
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 (
49
            basename in _HOOKS and
50
            basename == hook_name and
51
            not filename.endswith('~')
52
        ):
53
            return os.path.abspath(os.path.join(hooks_dir, f))
54
55
    return None
56
57
58
def run_script(script_path, cwd='.'):
59
    """Execute a script from a working directory.
60
61
    :param script_path: Absolute path to the script to run.
62
    :param cwd: The directory to run the script from.
63
    """
64
    run_thru_shell = sys.platform.startswith('win')
65
    if script_path.endswith('.py'):
66
        script_command = [sys.executable, script_path]
67
    else:
68
        script_command = [script_path]
69
70
    utils.make_executable(script_path)
71
72
    proc = subprocess.Popen(
73
        script_command,
74
        shell=run_thru_shell,
75
        cwd=cwd
76
    )
77
    exit_status = proc.wait()
78
    if exit_status != EXIT_SUCCESS:
79
        raise FailedHookException(
80
            "Hook script failed (exit status: %d)" % exit_status)
81
82
83
def run_script_with_context(script_path, cwd, context):
84
    """Execute a script after rendering it with Jinja.
85
86
    :param script_path: Absolute path to the script to run.
87
    :param cwd: The directory to run the script from.
88
    :param context: Cookiecutter project template context.
89
    """
90
    _, extension = os.path.splitext(script_path)
91
92
    contents = io.open(script_path, 'r', encoding='utf-8').read()
93
94
    with tempfile.NamedTemporaryFile(
95
        delete=False,
96
        mode='wb',
97
        suffix=extension
98
    ) as temp:
99
        output = Template(contents).render(**context)
100
        temp.write(output.encode('utf-8'))
101
102
    run_script(temp.name, cwd)
103
104
105
def run_hook(hook_name, project_dir, context):
106
    """
107
    Try to find and execute a hook from the specified project directory.
108
109
    :param hook_name: The hook to execute.
110
    :param project_dir: The directory to execute the script from.
111
    :param context: Cookiecutter project context.
112
    """
113
    script = find_hook(hook_name)
114
    if script is None:
115
        logger.debug('No hooks found')
116
        return
117
    logger.debug('Running hook {}'.format(hook_name))
118
    run_script_with_context(script, project_dir, context)
119