render_variable()   C
last analyzed

Complexity

Conditions 7

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
dl 0
loc 35
rs 5.5
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
3
"""
4
cookiecutter.prompt
5
---------------------
6
7
Functions for prompting the user for project info.
8
"""
9
10
from collections import OrderedDict
11
import json
12
13
import click
14
from past.builtins import basestring
15
16
from future.utils import iteritems
17
18
from jinja2.exceptions import UndefinedError
19
20
from .exceptions import UndefinedVariableInTemplate
21
from .environment import StrictEnvironment
22
23
24
def read_user_variable(var_name, default_value):
25
    """Prompt the user for the given variable and return the entered value
26
    or the given default.
27
28
    :param str var_name: Variable of the context to query the user
29
    :param default_value: Value that will be returned if no input happens
30
    """
31
    # Please see http://click.pocoo.org/4/api/#click.prompt
32
    return click.prompt(var_name, default=default_value)
33
34
35
def read_user_yes_no(question, default_value):
36
    """Prompt the user to reply with 'yes' or 'no' (or equivalent values).
37
38
    Note:
39
      Possible choices are 'true', '1', 'yes', 'y' or 'false', '0', 'no', 'n'
40
41
    :param str question: Question to the user
42
    :param default_value: Value that will be returned if no input happens
43
    """
44
    # Please see http://click.pocoo.org/4/api/#click.prompt
45
    return click.prompt(
46
        question,
47
        default=default_value,
48
        type=click.BOOL
49
    )
50
51
52
def read_repo_password(question):
53
    """Prompt the user to enter a password
54
55
    :param str question: Question to the user
56
    """
57
    # Please see http://click.pocoo.org/4/api/#click.prompt
58
    return click.prompt(question, hide_input=True)
59
60
61
def read_user_choice(var_name, options):
62
    """Prompt the user to choose from several options for the given variable.
63
64
    The first item will be returned if no input happens.
65
66
    :param str var_name: Variable as specified in the context
67
    :param list options: Sequence of options that are available to select from
68
    :return: Exactly one item of ``options`` that has been chosen by the user
69
    """
70
    # Please see http://click.pocoo.org/4/api/#click.prompt
71
    if not isinstance(options, list):
72
        raise TypeError
73
74
    if not options:
75
        raise ValueError
76
77
    choice_map = OrderedDict(
78
        (u'{}'.format(i), value) for i, value in enumerate(options, 1)
79
    )
80
    choices = choice_map.keys()
81
    default = u'1'
82
83
    choice_lines = [u'{} - {}'.format(*c) for c in choice_map.items()]
84
    prompt = u'\n'.join((
85
        u'Select {}:'.format(var_name),
86
        u'\n'.join(choice_lines),
87
        u'Choose from {}'.format(u', '.join(choices))
88
    ))
89
90
    user_choice = click.prompt(
91
        prompt, type=click.Choice(choices), default=default
92
    )
93
    return choice_map[user_choice]
94
95
96
def process_json(user_value):
97
    try:
98
        user_dict = json.loads(
99
            user_value,
100
            object_pairs_hook=OrderedDict,
101
        )
102
    except Exception:
103
        # Leave it up to click to ask the user again
104
        raise click.UsageError('Unable to decode to JSON.')
105
106
    if not isinstance(user_dict, dict):
107
        # Leave it up to click to ask the user again
108
        raise click.UsageError('Requires JSON dict.')
109
110
    return user_dict
111
112
113
def read_user_dict(var_name, default_value):
114
    """Prompt the user to provide a dictionary of data.
115
116
    :param str var_name: Variable as specified in the context
117
    :param default_value: Value that will be returned if no input is provided
118
    :return: A Python dictionary to use in the context.
119
    """
120
    # Please see http://click.pocoo.org/4/api/#click.prompt
121
    if not isinstance(default_value, dict):
122
        raise TypeError
123
124
    default_display = 'default'
125
126
    user_value = click.prompt(
127
        var_name,
128
        default=default_display,
129
        type=click.STRING,
130
        value_proc=process_json,
131
    )
132
133
    if user_value == default_display:
134
        # Return the given default w/o any processing
135
        return default_value
136
    return user_value
137
138
139
def render_variable(env, raw, cookiecutter_dict):
140
    """Inside the prompting taken from the cookiecutter.json file, this renders
141
    the next variable. For example, if a project_name is "Peanut Butter
142
    Cookie", the repo_name could be be rendered with:
143
144
        `{{ cookiecutter.project_name.replace(" ", "_") }}`.
145
146
    This is then presented to the user as the default.
147
148
    :param Environment env: A Jinja2 Environment object.
149
    :param str raw: The next value to be prompted for by the user.
150
    :param dict cookiecutter_dict: The current context as it's gradually
151
        being populated with variables.
152
    :return: The rendered value for the default variable.
153
    """
154
    if raw is None:
155
        return None
156
    elif isinstance(raw, dict):
157
        return {
158
            render_variable(env, k, cookiecutter_dict):
159
                render_variable(env, v, cookiecutter_dict)
160
            for k, v in raw.items()
161
        }
162
    elif isinstance(raw, list):
163
        return [
164
            render_variable(env, v, cookiecutter_dict)
165
            for v in raw
166
        ]
167
    elif not isinstance(raw, basestring):
168
        raw = str(raw)
169
170
    template = env.from_string(raw)
171
172
    rendered_template = template.render(cookiecutter=cookiecutter_dict)
173
    return rendered_template
174
175
176
def prompt_choice_for_config(cookiecutter_dict, env, key, options, no_input):
177
    """Prompt the user which option to choose from the given. Each of the
178
    possible choices is rendered beforehand.
179
    """
180
    rendered_options = [
181
        render_variable(env, raw, cookiecutter_dict) for raw in options
182
    ]
183
184
    if no_input:
185
        return rendered_options[0]
186
    return read_user_choice(key, rendered_options)
187
188
189
def prompt_for_config(context, no_input=False):
190
    """
191
    Prompts the user to enter new config, using context as a source for the
192
    field names and sample values.
193
194
    :param no_input: Prompt the user at command line for manual configuration?
195
    """
196
    cookiecutter_dict = {}
197
    env = StrictEnvironment(context=context)
198
199
    # First pass: Handle simple and raw variables, plus choices.
200
    # These must be done first because the dictionaries keys and
201
    # values might refer to them.
202
    for key, raw in iteritems(context[u'cookiecutter']):
203
        if key.startswith(u'_'):
204
            cookiecutter_dict[key] = raw
205
            continue
206
207
        try:
208
            if isinstance(raw, list):
209
                # We are dealing with a choice variable
210
                val = prompt_choice_for_config(
211
                    cookiecutter_dict, env, key, raw, no_input
212
                )
213
                cookiecutter_dict[key] = val
214
            elif not isinstance(raw, dict):
215
                # We are dealing with a regular variable
216
                val = render_variable(env, raw, cookiecutter_dict)
217
218
                if not no_input:
219
                    val = read_user_variable(key, val)
220
221
                cookiecutter_dict[key] = val
222
        except UndefinedError as err:
223
            msg = "Unable to render variable '{}'".format(key)
224
            raise UndefinedVariableInTemplate(msg, err, context)
225
226
    # Second pass; handle the dictionaries.
227
    for key, raw in iteritems(context[u'cookiecutter']):
228
229
        try:
230
            if isinstance(raw, dict):
231
                # We are dealing with a dict variable
232
                val = render_variable(env, raw, cookiecutter_dict)
233
234
                if not no_input:
235
                    val = read_user_dict(key, val)
236
237
                cookiecutter_dict[key] = val
238
        except UndefinedError as err:
239
            msg = "Unable to render variable '{}'".format(key)
240
            raise UndefinedVariableInTemplate(msg, err, context)
241
242
    return cookiecutter_dict
243