cookiecutter.utils   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 119
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 13
eloc 56
dl 0
loc 119
rs 10
c 0
b 0
f 0

7 Functions

Rating   Name   Duplication   Size   Complexity  
A make_executable() 0 7 1
A work_in() 0 13 2
A rmtree() 0 6 1
A force_delete() 0 8 1
A simple_filter() 0 10 1
A prompt_and_delete() 0 36 5
A make_sure_path_exists() 0 10 2
1
"""Helper functions used throughout Cookiecutter."""
2
import contextlib
3
import logging
4
import os
5
import shutil
6
import stat
7
import sys
8
from pathlib import Path
9
10
from jinja2.ext import Extension
11
12
from cookiecutter.prompt import read_user_yes_no
13
14
logger = logging.getLogger(__name__)
15
16
17
def force_delete(func, path, exc_info):
18
    """Error handler for `shutil.rmtree()` equivalent to `rm -rf`.
19
20
    Usage: `shutil.rmtree(path, onerror=force_delete)`
21
    From https://docs.python.org/3/library/shutil.html#rmtree-example
22
    """
23
    os.chmod(path, stat.S_IWRITE)
24
    func(path)
25
26
27
def rmtree(path):
28
    """Remove a directory and all its contents. Like rm -rf on Unix.
29
30
    :param path: A directory path.
31
    """
32
    shutil.rmtree(path, onerror=force_delete)
33
34
35
def make_sure_path_exists(path: "os.PathLike[str]") -> None:
36
    """Ensure that a directory exists.
37
38
    :param path: A directory tree path for creation.
39
    """
40
    logger.debug('Making sure path exists (creates tree if not exist): %s', path)
41
    try:
42
        Path(path).mkdir(parents=True, exist_ok=True)
43
    except OSError as error:
44
        raise OSError(f'Unable to create directory at {path}') from error
45
46
47
@contextlib.contextmanager
48
def work_in(dirname=None):
49
    """Context manager version of os.chdir.
50
51
    When exited, returns to the working directory prior to entering.
52
    """
53
    curdir = os.getcwd()
54
    try:
55
        if dirname is not None:
56
            os.chdir(dirname)
57
        yield
58
    finally:
59
        os.chdir(curdir)
60
61
62
def make_executable(script_path):
63
    """Make `script_path` executable.
64
65
    :param script_path: The file to change
66
    """
67
    status = os.stat(script_path)
68
    os.chmod(script_path, status.st_mode | stat.S_IEXEC)
69
70
71
def prompt_and_delete(path, no_input=False):
72
    """
73
    Ask user if it's okay to delete the previously-downloaded file/directory.
74
75
    If yes, delete it. If no, checks to see if the old version should be
76
    reused. If yes, it's reused; otherwise, Cookiecutter exits.
77
78
    :param path: Previously downloaded zipfile.
79
    :param no_input: Suppress prompt to delete repo and just delete it.
80
    :return: True if the content was deleted
81
    """
82
    # Suppress prompt if called via API
83
    if no_input:
84
        ok_to_delete = True
85
    else:
86
        question = (
87
            f"You've downloaded {path} before. Is it okay to delete and re-download it?"
88
        )
89
90
        ok_to_delete = read_user_yes_no(question, 'yes')
91
92
    if ok_to_delete:
93
        if os.path.isdir(path):
94
            rmtree(path)
95
        else:
96
            os.remove(path)
97
        return True
98
    else:
99
        ok_to_reuse = read_user_yes_no(
100
            "Do you want to re-use the existing version?", 'yes'
101
        )
102
103
        if ok_to_reuse:
104
            return False
105
106
        sys.exit()
107
108
109
def simple_filter(filter_function):
110
    """Decorate a function to wrap it in a simplified jinja2 extension."""
111
112
    class SimpleFilterExtension(Extension):
113
        def __init__(self, environment):
114
            super().__init__(environment)
115
            environment.filters[filter_function.__name__] = filter_function
116
117
    SimpleFilterExtension.__name__ = filter_function.__name__
118
    return SimpleFilterExtension
119