Passed
Push — master ( af4474...591779 )
by Andrey
04:46
created

cookiecutter.cli.list_installed_templates()   A

Complexity

Conditions 3

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 21
rs 9.6
c 0
b 0
f 0
cc 3
nop 2
1
"""Main `cookiecutter` CLI."""
2
import collections
3
import json
4
import os
5
import sys
6
7
import click
8
9
from cookiecutter import __version__
10
from cookiecutter.exceptions import (
11
    FailedHookException,
12
    InvalidModeException,
13
    InvalidZipRepository,
14
    OutputDirExistsException,
15
    RepositoryCloneFailed,
16
    RepositoryNotFound,
17
    UndefinedVariableInTemplate,
18
    UnknownExtension,
19
)
20
from cookiecutter.log import configure_logger
21
from cookiecutter.main import cookiecutter
22
from cookiecutter.config import get_user_config
23
24
25
def version_msg():
26
    """Return the Cookiecutter version, location and Python powering it."""
27
    python_version = sys.version[:3]
28
    location = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
29
    message = 'Cookiecutter %(version)s from {} (Python {})'
30
    return message.format(location, python_version)
31
32
33
def validate_extra_context(ctx, param, value):
34
    """Validate extra context."""
35
    for s in value:
36
        if '=' not in s:
37
            raise click.BadParameter(
38
                'EXTRA_CONTEXT should contain items of the form key=value; '
39
                "'{}' doesn't match that form".format(s)
40
            )
41
42
    # Convert tuple -- e.g.: ('program_name=foobar', 'startsecs=66')
43
    # to dict -- e.g.: {'program_name': 'foobar', 'startsecs': '66'}
44
    return collections.OrderedDict(s.split('=', 1) for s in value) or None
45
46
47
def list_installed_templates(default_config, passed_config_file):
48
    """List installed (locally cloned) templates. Use cookiecutter --list-installed."""
49
    config = get_user_config(passed_config_file, default_config)
50
    cookiecutter_folder = config.get('cookiecutters_dir')
51
    if not os.path.exists(cookiecutter_folder):
52
        click.echo(
53
            'Error: Cannot list installed templates. Folder does not exist: '
54
            '{}'.format(cookiecutter_folder)
55
        )
56
        sys.exit(-1)
57
58
    template_names = [
59
        folder
60
        for folder in os.listdir(cookiecutter_folder)
61
        if os.path.exists(
62
            os.path.join(cookiecutter_folder, folder, 'cookiecutter.json')
63
        )
64
    ]
65
    click.echo('{} installed templates: '.format(len(template_names)))
66
    for name in template_names:
67
        click.echo(' * {}'.format(name))
68
69
70
@click.command(context_settings=dict(help_option_names=['-h', '--help']))
71
@click.version_option(__version__, '-V', '--version', message=version_msg())
72
@click.argument('template', required=False)
73
@click.argument('extra_context', nargs=-1, callback=validate_extra_context)
74
@click.option(
75
    '--no-input',
76
    is_flag=True,
77
    help='Do not prompt for parameters and only use cookiecutter.json file content',
78
)
79
@click.option(
80
    '-c', '--checkout', help='branch, tag or commit to checkout after git clone',
81
)
82
@click.option(
83
    '--directory',
84
    help='Directory within repo that holds cookiecutter.json file '
85
    'for advanced repositories with multi templates in it',
86
)
87
@click.option(
88
    '-v', '--verbose', is_flag=True, help='Print debug information', default=False
89
)
90
@click.option(
91
    '--replay',
92
    is_flag=True,
93
    help='Do not prompt for parameters and only use information entered previously',
94
)
95
@click.option(
96
    '--replay-file',
97
    type=click.Path(),
98
    default=None,
99
    help='Use this file for replay instead of the default.',
100
)
101
@click.option(
102
    '-f',
103
    '--overwrite-if-exists',
104
    is_flag=True,
105
    help='Overwrite the contents of the output directory if it already exists',
106
)
107
@click.option(
108
    '-s',
109
    '--skip-if-file-exists',
110
    is_flag=True,
111
    help='Skip the files in the corresponding directories if they already exist',
112
    default=False,
113
)
114
@click.option(
115
    '-o',
116
    '--output-dir',
117
    default='.',
118
    type=click.Path(),
119
    help='Where to output the generated project dir into',
120
)
121
@click.option(
122
    '--config-file', type=click.Path(), default=None, help='User configuration file'
123
)
124
@click.option(
125
    '--default-config',
126
    is_flag=True,
127
    help='Do not load a config file. Use the defaults instead',
128
)
129
@click.option(
130
    '--debug-file',
131
    type=click.Path(),
132
    default=None,
133
    help='File to be used as a stream for DEBUG logging',
134
)
135
@click.option(
136
    '--accept-hooks',
137
    type=click.Choice(['yes', 'ask', 'no']),
138
    default='yes',
139
    help='Accept pre/post hooks',
140
)
141
@click.option(
142
    '-l', '--list-installed', is_flag=True, help='List currently installed templates.'
143
)
144
def main(
145
    template,
146
    extra_context,
147
    no_input,
148
    checkout,
149
    verbose,
150
    replay,
151
    overwrite_if_exists,
152
    output_dir,
153
    config_file,
154
    default_config,
155
    debug_file,
156
    directory,
157
    skip_if_file_exists,
158
    accept_hooks,
159
    replay_file,
160
    list_installed,
161
):
162
    """Create a project from a Cookiecutter project template (TEMPLATE).
163
164
    Cookiecutter is free and open source software, developed and managed by
165
    volunteers. If you would like to help out or fund the project, please get
166
    in touch at https://github.com/audreyr/cookiecutter.
167
    """
168
    # Commands that should work without arguments
169
    if list_installed:
170
        list_installed_templates(default_config, config_file)
171
        sys.exit(0)
172
173
    # Raising usage, after all commands that should work without args.
174
    if not template or template.lower() == 'help':
175
        click.echo(click.get_current_context().get_help())
176
        sys.exit(0)
177
178
    configure_logger(stream_level='DEBUG' if verbose else 'INFO', debug_file=debug_file)
179
180
    # If needed, prompt the user to ask whether or not they want to execute
181
    # the pre/post hooks.
182
    if accept_hooks == "ask":
183
        _accept_hooks = click.confirm("Do you want to execute hooks?")
184
    else:
185
        _accept_hooks = accept_hooks == "yes"
186
187
    if replay_file:
188
        replay = replay_file
189
190
    try:
191
        cookiecutter(
192
            template,
193
            checkout,
194
            no_input,
195
            extra_context=extra_context,
196
            replay=replay,
197
            overwrite_if_exists=overwrite_if_exists,
198
            output_dir=output_dir,
199
            config_file=config_file,
200
            default_config=default_config,
201
            password=os.environ.get('COOKIECUTTER_REPO_PASSWORD'),
202
            directory=directory,
203
            skip_if_file_exists=skip_if_file_exists,
204
            accept_hooks=_accept_hooks,
205
        )
206
    except (
207
        OutputDirExistsException,
208
        InvalidModeException,
209
        FailedHookException,
210
        UnknownExtension,
211
        InvalidZipRepository,
212
        RepositoryNotFound,
213
        RepositoryCloneFailed,
214
    ) as e:
215
        click.echo(e)
216
        sys.exit(1)
217
    except UndefinedVariableInTemplate as undefined_err:
218
        click.echo('{}'.format(undefined_err.message))
219
        click.echo('Error message: {}'.format(undefined_err.error.message))
220
221
        context_str = json.dumps(undefined_err.context, indent=4, sort_keys=True)
222
        click.echo('Context: {}'.format(context_str))
223
        sys.exit(1)
224
225
226
if __name__ == "__main__":
227
    main()
228