much smarter input_options to simplify its callers

This commit is contained in:
Adrian Sampson 2011-04-15 21:11:43 -07:00
parent 375e335002
commit 6031f2dac7
2 changed files with 95 additions and 39 deletions

View file

@ -73,18 +73,91 @@ def print_(*strings):
txt = txt.encode(encoding, 'replace')
print txt
def input_options(prompt, options, default=None,
fallback_prompt=None, numrange=None):
"""Prompts a user for input. The input must be one of the single
letters in options, a list of single-letter strings, or an integer
in numrange, which is a (low, high) tuple. If nothing is entered,
assume the input is default (if provided). Returns the value
entered, a single-letter string or an integer. If an incorrect
input occurs, fallback_prompt is used (by default identical to
the initial prompt).
def input_options(options, require=False, prompt=None, fallback_prompt=None,
numrange=None, default=None):
"""Prompts a user for input. The sequence of `options` defines the
choices the user has. A single-letter shortcut is inferred for each
option; the user's choice is returned as that single, lower-case
letter. The options should be provided as lower-case strings unless
a particular shortcut is desired; in that case, only that letter
should be capitalized.
By default, the first option is the default. If `require` is
provided, then there is no default. `default` can be provided to
override this. The prompt and fallback prompt are also inferred but
can be overridden.
If numrange is provided, it is a pair of `(high, low)` (both ints)
indicating that, in addition to `options`, the user may enter an
integer in that inclusive range.
"""
fallback_prompt = fallback_prompt or prompt
# Assign single letters to each option. Also capitalize the options
# to indicate the letter.
letters = {}
display_letters = []
capitalized = []
first = True
for option in options:
# Is a letter already capitalized?
for letter in option:
if letter.isalpha() and letter.upper() == letter:
found_letter = letter
break
else:
# Infer a letter.
for letter in option:
if not letter.isalpha():
continue # Don't use punctuation.
if letter not in letters:
found_letter = letter
break
else:
raise ValueError('no unambiguous lettering found')
letters[found_letter.lower()] = option
index = option.index(found_letter)
if (default is None and not numrange and first) \
or (isinstance(default, basestring) and
found_letter.lower() == default.lower()):
# The first option is the default; mark it.
show_letter = '[%s]' % found_letter.upper()
else:
show_letter = found_letter.upper()
capitalized.append(
option[:index] + show_letter + option[index+1:]
)
display_letters.append(found_letter.upper())
first = False
# The default is just the first option if unspecified.
if default is None:
if require:
default = None
elif numrange:
default = numrange[0]
else:
default = display_letters[0].lower()
# Make a prompt if one is not provided.
if not prompt:
if numrange:
if isinstance(default, int):
prompt = '# selection (default %i), ' % default
else:
prompt = '# selection, '
else:
prompt = ''
prompt += ', '.join(capitalized) + '?'
# Make a fallback prompt too. This is displayed if the user enters
# something that is not recognized.
if not fallback_prompt:
fallback_prompt = 'Enter one of '
if numrange:
fallback_prompt += '%i-%i, ' % numrange
fallback_prompt += ', '.join(display_letters) + ':'
resp = raw_input(prompt + ' ')
while True:
resp = resp.strip().lower()
@ -94,7 +167,7 @@ def input_options(prompt, options, default=None,
resp = default
# Try an integer input if available.
if numrange is not None:
if numrange:
try:
resp = int(resp)
except ValueError:
@ -109,25 +182,20 @@ def input_options(prompt, options, default=None,
# Try a normal letter input.
if resp:
resp = resp[0]
if resp in options:
if resp in letters:
return resp
# Prompt for new input.
resp = raw_input(fallback_prompt + ' ')
def input_yn(prompt, require=False):
"""Prompts user for a "yes" or "no" response where an empty response
is treated as "yes". Keeps prompting until acceptable input is
given; returns a boolean. If require is True, then an empty response
is not accepted.
"""Prompts the user for a "yes" or "no" response. The default is
"yes" unless `require` is `True`, in which case there is no default.
"""
sel = input_options(
prompt,
('y', 'n'),
None if require else 'y',
"Type 'y' or 'n':"
('y', 'n'), require, prompt, 'Enter Y or N:'
)
return (sel == 'y')
return sel == 'y'
def make_query(criteria):
"""Make query string for the list of criteria."""

View file

@ -185,11 +185,8 @@ def choose_candidate(candidates, singleton, rec, color,
# Zero candidates.
if not candidates:
print_("No match found.")
sel = ui.input_options(
"[U]se as-is, as Tracks, Skip, Enter search, or aBort?",
('u', 't', 's', 'e', 'b'), 'u',
'Enter U, T, S, E, or B:'
)
sel = ui.input_options(('Use as-is', 'as Tracks', 'Skip',
'Enter search', 'aBort'))
if sel == 'u':
return importer.action.ASIS
elif sel == 't':
@ -232,13 +229,8 @@ def choose_candidate(candidates, singleton, rec, color,
info['album'], dist_string(dist, color)))
# Ask the user for a choice.
sel = ui.input_options(
'# selection (default 1), Skip, Use as-is, as Tracks, '
'Enter search, or aBort?',
('s', 'u', 't', 'e', 'b'), '1',
'Enter a numerical selection, S, U, T, E, or B:',
(1, len(candidates))
)
sel = ui.input_options(('Skip', 'Use as-is', 'as Tracks',
'Enter search', 'aBort'), numrange=(1, len(candidates)))
if sel == 's':
return importer.action.SKIP
elif sel == 'u':
@ -270,12 +262,8 @@ def choose_candidate(candidates, singleton, rec, color,
return info, items
# Ask for confirmation.
sel = ui.input_options(
'[A]pply, More candidates, Skip, Use as-is, as Tracks, '
'Enter search, or aBort?',
('a', 'm', 's', 'u', 't', 'e', 'b'), 'a',
'Enter A, M, S, U, T, E, or B:'
)
sel = ui.input_options('Apply', 'More candidates', 'Skip', 'Use as-is',
'as Tracks', 'Enter search', 'aBort')
if sel == 'a':
if singleton:
return info