Completed
Push — master ( 2513d4...5ebdf1 )
by
unknown
01:04
created

read_user_dict()   A

Complexity

Conditions 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
c 0
b 0
f 0
dl 0
loc 18
rs 9.4285
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 read_user_dict(var_name, default_value):
88
    """Prompt the user to provide a dictionary of data.
89
90
    :param str var_name: Variable as specified in the context
91
    :param default_value: Value that will be returned if no input is provided
92
    :return: A Python dictionary to use in the context.
93
    """
94
    # Please see http://click.pocoo.org/4/api/#click.prompt
95
    if not isinstance(default_value, dict):
96
        raise TypeError
97
98
    raw = click.prompt(var_name, default='default')
99
    if raw != 'default':
100
        value = json.loads(raw, object_hook=OrderedDict)
101
    else:
102
        value = default_value
103
104
    return value
105
106
107
def render_variable(env, raw, cookiecutter_dict):
108
    if raw is None:
109
        return None
110
    elif isinstance(raw, dict):
111
        return {
112
            render_variable(env, k, cookiecutter_dict):
113
                render_variable(env, v, cookiecutter_dict)
114
            for k, v in raw.items()
115
        }
116
    elif isinstance(raw, list):
117
        return [
118
            render_variable(env, v, cookiecutter_dict)
119
            for v in raw
120
        ]
121
    elif not isinstance(raw, basestring):
122
        raw = str(raw)
123
124
    template = env.from_string(raw)
125
126
    rendered_template = template.render(cookiecutter=cookiecutter_dict)
127
    return rendered_template
128
129
130
def prompt_choice_for_config(cookiecutter_dict, env, key, options, no_input):
131
    """Prompt the user which option to choose from the given. Each of the
132
    possible choices is rendered beforehand.
133
    """
134
    rendered_options = [
135
        render_variable(env, raw, cookiecutter_dict) for raw in options
136
    ]
137
138
    if no_input:
139
        return rendered_options[0]
140
    return read_user_choice(key, rendered_options)
141
142
143
def prompt_for_config(context, no_input=False):
144
    """
145
    Prompts the user to enter new config, using context as a source for the
146
    field names and sample values.
147
148
    :param no_input: Prompt the user at command line for manual configuration?
149
    """
150
    cookiecutter_dict = {}
151
    env = StrictEnvironment(context=context)
152
153
    # First pass: Handle simple and raw variables, plus choices.
154
    # These must be done first because the dictionaries keys and
155
    # values might refer to them.
156
    for key, raw in iteritems(context[u'cookiecutter']):
157
        if key.startswith(u'_'):
158
            cookiecutter_dict[key] = raw
159
            continue
160
161
        try:
162
            if isinstance(raw, list):
163
                # We are dealing with a choice variable
164
                val = prompt_choice_for_config(
165
                    cookiecutter_dict, env, key, raw, no_input
166
                )
167
                cookiecutter_dict[key] = val
168
            elif not isinstance(raw, dict):
169
                # We are dealing with a regular variable
170
                val = render_variable(env, raw, cookiecutter_dict)
171
172
                if not no_input:
173
                    val = read_user_variable(key, val)
174
175
                cookiecutter_dict[key] = val
176
        except UndefinedError as err:
177
            msg = "Unable to render variable '{}'".format(key)
178
            raise UndefinedVariableInTemplate(msg, err, context)
179
180
    # Second pass; handle the dictionaries.
181
    for key, raw in iteritems(context[u'cookiecutter']):
182
183
        try:
184
            if isinstance(raw, dict):
185
                # We are dealing with a dict variable
186
                val = render_variable(env, raw, cookiecutter_dict)
187
188
                if not no_input:
189
                    val = read_user_dict(key, val)
190
191
                cookiecutter_dict[key] = val
192
        except UndefinedError as err:
193
            msg = "Unable to render variable '{}'".format(key)
194
            raise UndefinedVariableInTemplate(msg, err, context)
195
196
    return cookiecutter_dict
197