Completed
Push — improve-find-hooks ( f48bba )
by Michael
01:13
created

find_hook()   B

Complexity

Conditions 4

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
dl 0
loc 23
rs 8.7972
c 0
b 0
f 0
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
EXIT_SUCCESS = 0
24
25
26
def find_hook(hook_name, hooks_dir='hooks'):
27
    """
28
    Must be called with the project template as the current working directory.
29
    Returns a dict of all hook scripts provided.
30
    Dict's key will be the hook/script's name, without extension, while
31
    values will be the absolute path to the script.
32
    Missing scripts will not be included in the returned dict.
33
34
    :param hook_name: The hook to find
35
    :return: The absolute path to script or None
36
    """
37
    logging.debug('hooks_dir is {0}'.format(hooks_dir))
38
39
    if not os.path.isdir(hooks_dir):
40
        logging.debug('No hooks/ dir in template_dir')
41
        return None
42
43
    for f in os.listdir(hooks_dir):
44
        basename = os.path.splitext(os.path.basename(f))[0]
45
        if basename == hook_name:
46
            return os.path.abspath(os.path.join(hooks_dir, f))
47
48
    return None
49
50
51
def run_script(script_path, cwd='.'):
52
    """
53
    Executes a script from a working directory.
54
55
    :param script_path: Absolute path to the script to run.
56
    :param cwd: The directory to run the script from.
57
    """
58
    run_thru_shell = sys.platform.startswith('win')
59
    if script_path.endswith('.py'):
60
        script_command = [sys.executable, script_path]
61
    else:
62
        script_command = [script_path]
63
64
    utils.make_executable(script_path)
65
66
    proc = subprocess.Popen(
67
        script_command,
68
        shell=run_thru_shell,
69
        cwd=cwd
70
    )
71
    exit_status = proc.wait()
72
    if exit_status != EXIT_SUCCESS:
73
        raise FailedHookException(
74
            "Hook script failed (exit status: %d)" % exit_status)
75
76
77
def run_script_with_context(script_path, cwd, context):
78
    """
79
    Executes a script after rendering with it 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_hook(hook_name)
109
    if script is None:
110
        logging.debug('No hooks found')
111
        return
112
    run_script_with_context(script, project_dir, context)
113