Completed
Push — master ( 964e5a...097759 )
by Michael
8s
created

find_hooks()   B

Complexity

Conditions 5

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
c 1
b 0
f 0
dl 0
loc 23
rs 8.2508
1
# -*- coding: utf-8 -*-
2
3
"""
4
cookiecutter.hooks
5
------------------
6
7
Functions for discovering and executing various cookiecutter hooks.
8
"""
9
10
import io
11
import logging
12
import os
13
import subprocess
14
import sys
15
import tempfile
16
17
from jinja2 import Template
18
19
from cookiecutter import utils
20
from .exceptions import FailedHookException
21
22
23
_HOOKS = [
24
    'pre_gen_project',
25
    'post_gen_project',
26
    # TODO: other hooks should be listed here
27
]
28
EXIT_SUCCESS = 0
29
30
31
def find_hooks():
32
    """
33
    Must be called with the project template as the current working directory.
34
    Returns a dict of all hook scripts provided.
35
    Dict's key will be the hook/script's name, without extension, while
36
    values will be the absolute path to the script.
37
    Missing scripts will not be included in the returned dict.
38
    """
39
    hooks_dir = 'hooks'
40
    hooks = {}
41
    logging.debug('hooks_dir is {0}'.format(hooks_dir))
42
43
    if not os.path.isdir(hooks_dir):
44
        logging.debug('No hooks/ dir in template_dir')
45
        return hooks
46
47
    for f in os.listdir(hooks_dir):
48
        filename = os.path.basename(f)
49
        basename = os.path.splitext(filename)[0]
50
51
        if basename in _HOOKS and not filename.endswith('~'):
52
            hooks[basename] = os.path.abspath(os.path.join(hooks_dir, f))
53
    return hooks
54
55
56
def run_script(script_path, cwd='.'):
57
    """
58
    Executes a script from a working directory.
59
60
    :param script_path: Absolute path to the script to run.
61
    :param cwd: The directory to run the script from.
62
    """
63
    run_thru_shell = sys.platform.startswith('win')
64
    if script_path.endswith('.py'):
65
        script_command = [sys.executable, script_path]
66
    else:
67
        script_command = [script_path]
68
69
    utils.make_executable(script_path)
70
71
    proc = subprocess.Popen(
72
        script_command,
73
        shell=run_thru_shell,
74
        cwd=cwd
75
    )
76
    exit_status = proc.wait()
77
    if exit_status != EXIT_SUCCESS:
78
        raise FailedHookException(
79
            "Hook script failed (exit status: %d)" % exit_status)
80
81
82
def run_script_with_context(script_path, cwd, context):
83
    """
84
    Executes a script after rendering with it 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_hooks().get(hook_name)
114
    if script is None:
115
        logging.debug('No hooks found')
116
        return
117
    run_script_with_context(script, project_dir, context)
118