mirror of
https://github.com/beetbox/beets.git
synced 2025-12-15 21:14:19 +01:00
much smarter input_options to simplify its callers
This commit is contained in:
parent
375e335002
commit
6031f2dac7
2 changed files with 95 additions and 39 deletions
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue