Completed
Push — master ( a8119a...52e3ec )
by
unknown
01:06
created

create_value_proc()   B

Complexity

Conditions 5

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
dl 0
loc 22
rs 8.3411
c 1
b 0
f 0

1 Method

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