1 """
2 A round of prompting the users for, and validating, answers.
3
4 These provide a simple, consistent and robust way of formatting prompts for
5 gathering information from a commandline user and validating their answers.
6 Users are prompted with a question and optionally explanatory help text and
7 hints of possible answers.
8
9 A question is usually formatted as follows::
10
11 helptext ... (multiple lines if need be) ... helptext
12 question (hints) [default]:
13
14 Multiple choice questions are formatted as::
15
16 helptext ... (multiple lines if need be) ... helptext
17 1. choice
18 2. choice
19 ...
20 N. choice
21 question (hints) [default]:
22
23 """
24
25
26 __docformat__ = "restructuredtext en"
27
28
29
30
31 import types
32
33 import defs
34 import validators
35
36 __all__ = [
37 'Session',
38 'prompt',
39 ]
40
41
42
43
44
45
47
48 ""
49
51 self.choice_delim = '/'
52
53
54 - def string (self, question, converters=[], help=None, hints=None,
55 default=None, convert_default=True,
56 strip_flanking_space=False):
57 """
58 Ask for and return text from the user.
59
60 The simplest public question function and the basis of many of the others,
61 this is a thin wrapper around the core `_ask` method that
62
63 """
64 return self._ask (question,
65 converters=converters,
66 help=help,
67 hints=hints,
68 default=default,
69 strip_flanking_space=strip_flanking_space,
70 multiline=False,
71 )
72
73 - def text (self, question, converters=[], help=None, hints=None, default=None,
74 strip_flanking_space=False):
75 """
76 Ask for and return text from the user.
77
78 The simplest public question function and the basis of many of the others,
79 this is a thin wrapper around the core `_ask` method that allows for
80 multi-line responses.
81
82 """
83 return self._ask (question,
84 converters=[],
85 help=help,
86 hints=hints,
87 default=default,
88 strip_flanking_space=strip_flanking_space,
89 multiline=True,
90 )
91
92 - def integer (self, question, converters=[], help=None, hints=None,
93 default=None, convert_default=True, min=None, max=None):
94 return self.string (question,
95 converters=[validators.ToInt(), validators.Range (min, max)] + converters,
96 help=help,
97 hints=hints,
98 default=default,
99 convert_default=convert_default,
100 strip_flanking_space=True,
101 )
102
103
104 - def short_choice (self, question, choice_str, converters=[], help=None, default=None):
105 """
106 Ask the user to make a choice using single letters.
107 """
108
109 choice_str = choice_str.strip().lower()
110 assert choice_str, "need choices for question"
111 if default:
112 default = default.lower()
113 assert (len(default) == 1), \
114 "ask_short_choice uses only single letters, not '%s'" % default
115
116 hints = choice_str
117
118 return self._ask (question,
119 converters= converters or [validators.Vocab(list(choice_str))],
120 help=help, hints=hints, default=default)
121
122
123 - def yesno (self, question, help=None, default=None):
134
136 """
137 Ask the user to make a choice from a list.
138
139 """
140
141 assert choices, "need choices for question"
142 if default:
143 default = default.lower()
144
145
146 synonyms = {}
147 vocab = []
148 menu = []
149 for i, c in enumerate (choices):
150 if isinstance (c, basestring):
151 val = c
152 desc = c
153 syns = []
154 elif instance_of (c, Choice):
155 val = c.value
156 desc = c.desc or value
157 syns = c.syns
158 else:
159 assert false, "shouldn't get here"
160 assert val not in vocab, "duplicate choice value '%s'" % val
161 vocab.append (val)
162 menu_index = str(i + 1)
163 syns.append(menu_index)
164 for s in syns:
165 assert not synonyms.has_key(s), "duplicate choice synonym '%s'" % s
166 synonyms[s] = val
167 menu.append (" %s. %s" % (menu_index, desc))
168 help = '\n'.join([help]+ menu).strip()
169
170
171 return self._ask (question,
172 converters=[
173 Synonyms(synonyms),
174 Vocab(vocab)
175 ],
176 help=help,
177 hints='1-%s' % len(choices),
178 default=default
179 )
180
181
182 - def _ask (self, question, converters=[], help=None, choices=[], hints=None,
183 default=None, convert_default=True, multiline=False,
184 strip_flanking_space=True):
185 """
186 Ask for and return an answer from the user.
187
188 :Parameters:
189 question
190 The text of the question asked.
191 converters
192 An array of conversion and validation functions to be called in
193 sucession with the results of the previous. If any throws an error,
194 it will be caught and the question asked again.
195 help
196 Introductory text to be shown before the question.
197 hints
198 Short reminder text of possible answers.
199 default
200 The value the answer will be set to before processing if a blank
201 answer (i.e. just hitting return) is entered.
202 convert_default
203 If the default value is used, it will be processed through the
204 converters. Otherwise it will be dircetly returned.
205 strip_flanking_space
206 If true, flanking space will be stripped from the answer before it is
207 processed.
208
209 This is the underlying function for getting information from the user. It
210 prints the help text (if any), any menu of choices, prints the question
211 and hints and then waits for input from the user. All answers are fed from
212 the converters. If conversion fails, the question is re-asked.
213
214 The following sequence is used in processing user answers:
215
216 1. The raw input is read
217 2. If the options is set, flanking space is stripped
218 3. If the input is an empty string and a default answer is given:
219
220 1. if convert_default is set, the input is set to that value (i.e.
221 the default answer must be a valid input value)
222
223 2. else return default value immediately (bypass conversion)
224
225 4. The input is feed through each converter in turn, with the the result
226 of one feeding into the next.
227 5. If the conversion raises an error, the question is asked again
228 6. Otherwise the processed answer is returned
229
230 """
231
232
233
234
235
236
237
238
239
240
241
242 assert (question), "'ask' requires a question"
243
244
245
246 if help:
247 print self._clean_text (help)
248 for c in choices:
249 print " %s" % c.lstrip()
250
251
252 question_str = self._clean_text ("%s%s: " % (
253 question, self._format_hints_text (hints, default)))
254
255
256 while True:
257 if multiline:
258 raw_answer = self.read_input_multiline (question_str)
259 else:
260 raw_answer = self.read_input_line (question_str)
261 if strip_flanking_space:
262 raw_answer = raw_answer.strip()
263
264
265 if (raw_answer == '') and (default is not None):
266 if convert_default:
267
268 raw_answer = default
269 else:
270
271 return default
272 try:
273 for conv in converters:
274 raw_answer = conv.__call__ (raw_answer)
275 except StandardError, err:
276 print "A problem: %s. Try again ..." % err
277 except:
278 print "A problem: unknown error. Try again ..."
279 else:
280 return raw_answer
281
282 - def _clean_text (self, text):
283 """
284 Trim, un-wrap and rewrap text to be presented to the user.
285 """
286
287
288 return defs.SPACE_RE.sub (' ', text.strip())
289
290
292 """
293 Consistently format hints and default values for inclusion in questions.
294
295 The hints section of the questions is formatted as::
296
297 (hint text) [default value text]
298
299 If hints or the default value are not supplied (i.e. they are set to None)
300 that section does not appear. If neither is supplied, an empty string is
301 returned.
302
303 Some heuristics are used in presentation. If the hints is a list (e.g. of
304 possible choices), these are formatted as a comma delimited list. If the
305 default value is a blank string, '' is given to make this explicit.
306
307 Note this does not check if the default is a valid value.
308
309 For example::
310
311 >>> print prompt._format_hints_text()
312 <BLANKLINE>
313 >>> print prompt._format_hints_text([1, 2, 3], 'foo')
314 (1,2,3) [foo]
315 >>> print prompt._format_hints_text('1-3', '')
316 (1-3) ['']
317 >>> print prompt._format_hints_text('an integer')
318 (an integer)
319
320 """
321 hints_str = ''
322 if hints is not None:
323
324 if type(hints) in [types.ListType, types.TupleType]:
325 hints = self.choice_delim.join (['%s' % x for x in hints])
326 hints_str = ' (%s)' % hints
327 if default is not None:
328
329 if default is '':
330 default = "''"
331 hints_str += ' [%s]' % default
332
333 return hints_str
334
343
369
370
371
372 prompt = Session()
373
374
375
376
377
378 if __name__ == "__main__":
379 import doctest
380 doctest.testmod()
381
382
383
384