1# -*- coding: utf-8 -*-
2
3"""Main `cookiecutter` CLI."""
4
5import os
6import sys
7import json
8import collections
9
10import click
11
12from cookiecutter import __version__
13from cookiecutter.log import configure_logger
14from cookiecutter.main import cookiecutter
15from cookiecutter.exceptions import (
16    OutputDirExistsException,
17    InvalidModeException,
18    FailedHookException,
19    UndefinedVariableInTemplate,
20    UnknownExtension,
21    InvalidZipRepository,
22    RepositoryNotFound,
23    RepositoryCloneFailed,
24)
25
26
27def version_msg():
28    """Return the Cookiecutter version, location and Python powering it."""
29    python_version = sys.version[:3]
30    location = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
31    message = u'Cookiecutter %(version)s from {} (Python {})'
32    return message.format(location, python_version)
33
34
35def validate_extra_context(ctx, param, value):
36    """Validate extra context."""
37    for s in value:
38        if '=' not in s:
39            raise click.BadParameter(
40                'EXTRA_CONTEXT should contain items of the form key=value; '
41                "'{}' doesn't match that form".format(s)
42            )
43
44    # Convert tuple -- e.g.: (u'program_name=foobar', u'startsecs=66')
45    # to dict -- e.g.: {'program_name': 'foobar', 'startsecs': '66'}
46    return collections.OrderedDict(s.split('=', 1) for s in value) or None
47
48
49@click.command(context_settings=dict(help_option_names=[u'-h', u'--help']))
50@click.version_option(__version__, u'-V', u'--version', message=version_msg())
51@click.argument(u'template')
52@click.argument(u'extra_context', nargs=-1, callback=validate_extra_context)
53@click.option(
54    u'--no-input',
55    is_flag=True,
56    help=u'Do not prompt for parameters and only use cookiecutter.json '
57    u'file content',
58)
59@click.option(
60    u'-c', u'--checkout', help=u'branch, tag or commit to checkout after git clone',
61)
62@click.option(
63    u'--directory',
64    help=u'Directory within repo that holds cookiecutter.json file '
65    u'for advanced repositories with multi templates in it',
66)
67@click.option(
68    '-v', '--verbose', is_flag=True, help='Print debug information', default=False
69)
70@click.option(
71    u'--replay',
72    is_flag=True,
73    help=u'Do not prompt for parameters and only use information entered '
74    u'previously',
75)
76@click.option(
77    u'-f',
78    u'--overwrite-if-exists',
79    is_flag=True,
80    help=u'Overwrite the contents of the output directory if it already exists',
81)
82@click.option(
83    u'-s',
84    u'--skip-if-file-exists',
85    is_flag=True,
86    help=u'Skip the files in the corresponding directories if they already ' u'exist',
87    default=False,
88)
89@click.option(
90    u'-o',
91    u'--output-dir',
92    default='.',
93    type=click.Path(),
94    help=u'Where to output the generated project dir into',
95)
96@click.option(
97    u'--config-file', type=click.Path(), default=None, help=u'User configuration file'
98)
99@click.option(
100    u'--default-config',
101    is_flag=True,
102    help=u'Do not load a config file. Use the defaults instead',
103)
104@click.option(
105    u'--debug-file',
106    type=click.Path(),
107    default=None,
108    help=u'File to be used as a stream for DEBUG logging',
109)
110def main(
111    template,
112    extra_context,
113    no_input,
114    checkout,
115    verbose,
116    replay,
117    overwrite_if_exists,
118    output_dir,
119    config_file,
120    default_config,
121    debug_file,
122    directory,
123    skip_if_file_exists,
124):
125    """Create a project from a Cookiecutter project template (TEMPLATE).
126
127    Cookiecutter is free and open source software, developed and managed by
128    volunteers. If you would like to help out or fund the project, please get
129    in touch at https://github.com/audreyr/cookiecutter.
130    """
131    # If you _need_ to support a local template in a directory
132    # called 'help', use a qualified path to the directory.
133    if template == u'help':
134        click.echo(click.get_current_context().get_help())
135        sys.exit(0)
136
137    configure_logger(stream_level='DEBUG' if verbose else 'INFO', debug_file=debug_file)
138
139    try:
140        cookiecutter(
141            template,
142            checkout,
143            no_input,
144            extra_context=extra_context,
145            replay=replay,
146            overwrite_if_exists=overwrite_if_exists,
147            output_dir=output_dir,
148            config_file=config_file,
149            default_config=default_config,
150            password=os.environ.get('COOKIECUTTER_REPO_PASSWORD'),
151            directory=directory,
152            skip_if_file_exists=skip_if_file_exists,
153        )
154    except (
155        OutputDirExistsException,
156        InvalidModeException,
157        FailedHookException,
158        UnknownExtension,
159        InvalidZipRepository,
160        RepositoryNotFound,
161        RepositoryCloneFailed,
162    ) as e:
163        click.echo(e)
164        sys.exit(1)
165    except UndefinedVariableInTemplate as undefined_err:
166        click.echo('{}'.format(undefined_err.message))
167        click.echo('Error message: {}'.format(undefined_err.error.message))
168
169        context_str = json.dumps(undefined_err.context, indent=4, sort_keys=True)
170        click.echo('Context: {}'.format(context_str))
171        sys.exit(1)
172
173
174if __name__ == "__main__":
175    main()
176