jenkins-bot has submitted this change and it was merged.
Change subject: [FEAT] input_choice: modular options ......................................................................
[FEAT] input_choice: modular options
This makes input_choice more modular an thus allowing nested options or numbers as options. It removes the check that each option is unique so the caller needs to take care of that. The options are now mutable so that they can represent changes. For example when the list changes and it's possible to select one item from the list.
Change-Id: Ib922bcdb77ee34e8d0268ded2b003e7e6a9005b4 --- M pywikibot/bot.py A pywikibot/bot_choice.py M pywikibot/userinterfaces/terminal_interface.py M pywikibot/userinterfaces/terminal_interface_base.py M scripts/catall.py M scripts/category.py M scripts/interwiki.py M scripts/solve_disambiguation.py 8 files changed, 542 insertions(+), 253 deletions(-)
Approvals: John Vandenberg: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/bot.py b/pywikibot/bot.py index b58351d..e4aba4e 100644 --- a/pywikibot/bot.py +++ b/pywikibot/bot.py @@ -43,6 +43,11 @@ from pywikibot import config from pywikibot import daemonize from pywikibot import version +from pywikibot.bot_choice import ( # noqa: unused imports + Option, StandardOption, NestedOption, IntegerOption, ContextOption, + ListOption, + ChoiceException, QuitKeyboardInterrupt, +) from pywikibot.tools import deprecated, deprecated_args, PY2
if not PY2: @@ -52,12 +57,9 @@ # search for user interface module in the 'userinterfaces' subdirectory uiModule = __import__("pywikibot.userinterfaces.%s_interface" % config.userinterface, - fromlist=['UI', 'ChoiceException', 'QuitKeyboardInterrupt']) + fromlist=['UI']) ui = uiModule.UI() pywikibot.argvu = ui.argvu() - -ChoiceException = uiModule.ChoiceException -QuitKeyboardInterrupt = uiModule.QuitKeyboardInterrupt
# It's not possible to use pywikibot.exceptions.PageRelatedError as that is diff --git a/pywikibot/bot_choice.py b/pywikibot/bot_choice.py new file mode 100755 index 0000000..95c1808 --- /dev/null +++ b/pywikibot/bot_choice.py @@ -0,0 +1,292 @@ +# -*- coding: utf-8 -*- +"""Choices for input_choice.""" +# +# (C) Pywikibot team, 2015 +# +# Distributed under the terms of the MIT license. +# +from __future__ import unicode_literals + +__version__ = '$Id$' + +import pywikibot + + +class Option(object): + + """ + A basic option for input_choice. + + The following methods need to be implemented: + * format(default) + * result(value) + * test(value) + + The methods C{test} and C{handled} are in such a relationship that when + C{handled} returns itself that C{test} must return True for that value. So + if C{test} returns False C{handled} may not return itself but it may return + not None. + + Also C{result} only returns a sensible value when C{test} returns True for + the same value. + """ + + def __init__(self, stop=True): + """Constructor.""" + super(Option, self).__init__() + self._stop = stop + + @staticmethod + def formatted(text, options, default): + """Create a text with the options formatted into it.""" + formatted_options = [] + for option in options: + formatted_options.append(option.format(default)) + return '{0} ({1})'.format(text, ', '.join(formatted_options)) + + @property + def stop(self): + """Return whether this option stops asking.""" + return self._stop + + def handled(self, value): + """ + Return the Option object that applies to the given value. + + If this Option object doesn't know which applies it returns None. + """ + if self.test(value): + return self + else: + return None + + def format(self, default): + """Return a formatted string for that option.""" + raise NotImplementedError() + + def result(self, value): + """Return the actual value which is associated by the given one.""" + raise NotImplementedError() + + def test(self, value): + """Return True whether this option applies.""" + raise NotImplementedError() + + +class OutputOption(Option): + + """An option that never stops and can output on each question.""" + + before_question = False + + @property + def stop(self): + """Never stop asking.""" + return False + + def result(self, value): + """Just output the value.""" + self.output() + + def output(self): + """Output a string when selected and possibily before the question.""" + raise NotImplementedError() + + +class StandardOption(Option): + + """An option with a description and shortcut and returning the shortcut.""" + + def __init__(self, option, shortcut, stop=True): + """Constructor.""" + super(StandardOption, self).__init__(stop) + self.option = option + self.shortcut = shortcut.lower() + + def format(self, default): + """Return a formatted string for that option.""" + index = self.option.lower().find(self.shortcut) + shortcut = self.shortcut + if self.shortcut == default: + shortcut = self.shortcut.upper() + if index >= 0: + return '{0}[{1}]{2}'.format(self.option[:index], shortcut, + self.option[index + len(self.shortcut):]) + else: + return '{0} [{1}]'.format(self.option, shortcut) + + def result(self, value): + """Return the lowercased shortcut.""" + return self.shortcut + + def test(self, value): + """Return True whether this option applies.""" + return (self.shortcut.lower() == value.lower() or + self.option.lower() == value.lower()) + + +class NestedOption(OutputOption, StandardOption): + + """ + An option containing other options. + + It will return True in test if this option applies but False if a sub + option applies while handle returns the sub option. + """ + + def __init__(self, option, shortcut, description, options): + """Constructor.""" + super(NestedOption, self).__init__(option, shortcut, False) + self.description = description + self.options = options + + def format(self, default): + """Return a formatted string for that option.""" + self._output = Option.formatted(self.description, self.options, default) + return super(NestedOption, self).format(default) + + def handled(self, value): + """Return itself if it applies or the appling sub option.""" + for option in self.options: + handled = option.handled(value) + if handled is not None: + return handled + else: + return super(NestedOption, self).handled(value) + + def output(self): + """Output the suboptions.""" + pywikibot.output(self._output) + + +class IntegerOption(Option): + + """An option allowing a range of integers.""" + + def __init__(self, minimum=1, maximum=None, prefix=''): + """Constructor.""" + super(IntegerOption, self).__init__() + if minimum is not None and maximum is not None and minimum >= maximum: + raise ValueError('The minimum must be lower than the maximum.') + self.minimum = minimum + self.maximum = maximum + self.prefix = prefix + + def test(self, value): + """Return whether the value is an int and in the specified range.""" + if not value.lower().startswith(self.prefix.lower()): + return False + try: + value = self.parse(value) + except ValueError: + return False + else: + return ((self.minimum is None or value >= self.minimum) and + (self.maximum is None or value <= self.maximum)) + + def format(self, default): + """Return a formatted string showing the range.""" + if self.minimum is not None or self.maximum is not None: + _min = '' if self.minimum is None else str(self.minimum) + _max = '' if self.maximum is None else str(self.maximum) + rng = _min + '-' + _max + else: + rng = 'any' + return self.prefix + '<number> [' + rng + ']' + + def parse(self, value): + """Return integer from value with prefix removed.""" + if value.lower().startswith(self.prefix.lower()): + return int(value[len(self.prefix):]) + else: + raise ValueError('Value does not start with prefix') + + def result(self, value): + """Return the value converted into int.""" + return (self.prefix, self.parse(value)) + + +class ContextOption(OutputOption, StandardOption): + + """An option to show more and more context.""" + + def __init__(self, option, shortcut, text, context, delta=100, start=0, end=0): + """Constructor.""" + super(ContextOption, self).__init__(option, shortcut, False) + self.text = text + self.context = context + self.delta = delta + self.start = start + self.end = end + + def result(self, value): + """Add the delta to the context and output it.""" + self.context += self.delta + super(ContextOption, self).result(value) + + def output(self): + """Output the context.""" + start = max(0, self.start - self.context) + end = min(len(self.text), self.end + self.context) + self.output_range(start, end) + + def output_range(self, start_context, end_context): + """Output a section from the text.""" + pywikibot.output(self.text[start_context:end_context]) + + +class ListOption(Option): + + """An option to select something from a list.""" + + def __init__(self, sequence, prefix): + """Constructor.""" + super(ListOption, self).__init__() + self._list = sequence + self._prefix = prefix + + def format(self, default): + """Return a string showing the range.""" + return '<number> [0-{0}]'.format(len(self._list) - 1) + + def test(self, value): + """Test if the value is an int and in range.""" + try: + value = int(value) + except ValueError: + return False + else: + return 0 <= value < len(self._list) + + def result(self, value): + """Return a tuple with the prefix and selected value.""" + return (self._prefix, self._list[int(value)]) + + +class HighlightContextOption(ContextOption): + + """Show the original region highlighted.""" + + def output_range(self, start, end): + """Show normal context with a red center region.""" + pywikibot.output(self.text[start:self.start] + '\03{lightred}' + + self.text[self.start:self.end] + '\03{default}' + + self.text[self.end:end]) + + +class ChoiceException(StandardOption, Exception): + + """A choice for input_choice which result in this exception.""" + + def result(self, value): + """Return itself to raise the exception.""" + return self + + +class QuitKeyboardInterrupt(ChoiceException, KeyboardInterrupt): + + """The user has cancelled processing at a prompt.""" + + def __init__(self): + """Constructor using the 'quit' ('q') in input_choice.""" + super(QuitKeyboardInterrupt, self).__init__('quit', 'q') diff --git a/pywikibot/userinterfaces/terminal_interface.py b/pywikibot/userinterfaces/terminal_interface.py index 768b5e8..9a5fbf1 100644 --- a/pywikibot/userinterfaces/terminal_interface.py +++ b/pywikibot/userinterfaces/terminal_interface.py @@ -20,10 +20,4 @@ else: from .terminal_interface_unix import UnixUI as UI
-from pywikibot.userinterfaces.terminal_interface_base import ( - ChoiceException, QuitKeyboardInterrupt, -) - -__all__ = ( - 'UI', 'ChoiceException', 'QuitKeyboardInterrupt', -) +__all__ = ('UI',) diff --git a/pywikibot/userinterfaces/terminal_interface_base.py b/pywikibot/userinterfaces/terminal_interface_base.py index fa69a1f..17bdd46 100755 --- a/pywikibot/userinterfaces/terminal_interface_base.py +++ b/pywikibot/userinterfaces/terminal_interface_base.py @@ -20,6 +20,9 @@ from pywikibot import config from pywikibot.bot import VERBOSE, INFO, STDOUT, INPUT, WARNING from pywikibot.tools import deprecated, PY2 +from pywikibot.bot_choice import ( + Option, OutputOption, StandardOption, ChoiceException, QuitKeyboardInterrupt, +)
transliterator = transliteration.transliterator(config.console_encoding)
@@ -44,25 +47,6 @@ ]
colorTagR = re.compile('\03{(?P<name>%s)}' % '|'.join(colors)) - - -class ChoiceException(Exception): - - """A choice for input_choice which result in this exception.""" - - def __init__(self, option, shortcut): - """Constructor using the given option and shortcut in input_choice.""" - self.option = option - self.shortcut = shortcut - - -class QuitKeyboardInterrupt(ChoiceException, KeyboardInterrupt): - - """The user has cancelled processing at a prompt.""" - - def __init__(self): - """Constructor using the 'quit' ('q') in input_choice.""" - super(QuitKeyboardInterrupt, self).__init__('quit', 'q')
class UI: @@ -287,15 +271,18 @@ """ Ask the user and returns a value from the options.
+ Depending on the options setting return_shortcut to False may not be + sensible when the option supports multiple values as it'll return an + ambiguous index. + @param question: The question, without trailing whitespace. @type question: basestring @param options: All available options. Each entry contains the full length answer and a shortcut of only one character. The shortcut must not appear in the answer. Alternatively they may be a - ChoiceException (or subclass) instance which has a full option and - shortcut. It will raise that exception when selected. - @type options: iterable containing sequences of length 2 or - ChoiceException + Option (or subclass) instance. ChoiceException instances which have + a full option and shortcut and will be raised if selected. + @type options: iterable containing sequences of length 2 or Option @param default: The default answer if no was entered. None to require an answer. @type default: basestring @@ -312,6 +299,8 @@ options. If default is not a shortcut, it'll return -1. @rtype: int (if not return_shortcut), lowercased basestring (otherwise) """ + if force and default is None: + raise ValueError('With no default option it cannot be forced') options = list(options) if len(options) == 0: raise ValueError(u'No options are given.') @@ -319,55 +308,39 @@ options += [QuitKeyboardInterrupt()] if default: default = default.lower() - valid = {} - default_index = -1 - formatted_options = [] for i, option in enumerate(options): - if isinstance(option, ChoiceException): - option, shortcut = option.option, option.shortcut - else: + if not isinstance(option, Option): if len(option) != 2: - raise ValueError('Option #{0} does not consist of an ' - 'option and shortcut.'.format(i)) - option, shortcut = option - if option.lower() in valid: - raise ValueError( - u'Multiple identical options ({0}).'.format(option)) - shortcut = shortcut.lower() - if shortcut in valid: - raise ValueError( - u'Multiple identical shortcuts ({0}).'.format(shortcut)) - valid[option.lower()] = i - valid[shortcut] = i - index = option.lower().find(shortcut) - if shortcut == default: - default_index = i - shortcut = shortcut.upper() - if index >= 0: - option = u'{0}[{1}]{2}'.format(option[:index], shortcut, - option[index + len(shortcut):]) - else: - option = u'{0} [{1}]'.format(option, shortcut) - formatted_options += [option] - question = u'{0} ({1})'.format(question, ', '.join(formatted_options)) - answer = None - while answer is None: + raise ValueError(u'Option #{0} does not consist of an ' + u'option and shortcut.'.format(i)) + options[i] = StandardOption(*option) + # TODO: Test for uniquity + + handled = False + while not handled: + for option in options: + if isinstance(option, OutputOption) and option.before_question: + option.output() + output = Option.formatted(question, options, default) if force: - self.output(question + '\n') + self.output(output + '\n') + answer = default else: - answer = self.input(question) - if default and not answer: # nothing entered - answer = default_index - else: - answer = valid.get(answer.lower(), None) - if isinstance(options[answer], ChoiceException): - raise options[answer] + answer = self.input(output) or default + # something entered or default is defined + if answer: + for index, option in enumerate(options): + if option.handled(answer): + answer = option.result(answer) + handled = option.stop + break + + if isinstance(answer, ChoiceException): + raise answer elif not return_shortcut: - return answer - elif answer < 0: - return default + return index else: - return options[answer][1].lower() + return answer
@deprecated('input_choice') def inputChoice(self, question, options, hotkeys, default=None): diff --git a/scripts/catall.py b/scripts/catall.py index a45eb28..4998c93 100755 --- a/scripts/catall.py +++ b/scripts/catall.py @@ -37,6 +37,7 @@ chosen = [] done = False length = 1000 + # TODO: → input_choice print("""Give the new categories, one per line. Empty line: if the first, don't change. Otherwise: Ready. -: I made a mistake, let me start over. diff --git a/scripts/category.py b/scripts/category.py index bda9623..51ce977 100755 --- a/scripts/category.py +++ b/scripts/category.py @@ -126,7 +126,9 @@ import pywikibot from pywikibot import config, pagegenerators from pywikibot import i18n, textlib -from pywikibot.bot import MultipleSitesBot +from pywikibot.bot import ( + MultipleSitesBot, IntegerOption, StandardOption, ContextOption, +) from pywikibot.tools import ( deprecated_args, deprecated, ModuleDeprecationWrapper ) @@ -866,6 +868,27 @@ NOTE: current_cat is only used for internal recursion. You should always use current_cat = original_cat. """ + class CatContextOption(ContextOption): + + """An option to show more and more context.""" + + def __init__(self): + """Constructor.""" + super(CatContextOption, self).__init__( + 'print first part of the page (longer and longer)', '?', + full_text, contextLength, 500) + + def output_range(self, start, end): + pywikibot.output('\n' + full_text[:end] + '\n') + + # if categories possibly weren't visible, show them additionally + # (maybe this should always be shown?) + if len(self.text) > end: + pywikibot.output('') + pywikibot.output('Original categories: ') + for cat in article.categories(): + pywikibot.output(u'* %s' % cat.title()) + pywikibot.output(u'') # Show the title of the page where the link was found. # Highlight the title in purple. @@ -890,7 +913,8 @@ if contextLength > 1000 or contextLength < 0: contextLength = 500
- pywikibot.output('\n' + full_text[:contextLength] + '\n') + context_option = CatContextOption() + context_option.output()
# we need list to index the choice subcatlist = list(self.catDB.getSubcats(current_cat)) @@ -907,69 +931,43 @@ for i, subcat in enumerate(subcatlist): # layout: we don't expect a cat to have more than 100 subcats pywikibot.output(u'%2d - Move down to %s' % (i, subcat.title())) - pywikibot.output(' j - Jump to another category\n' - ' s - Skip this article\n' - ' r - Remove this category tag\n' - ' ? - Print first part of the page (longer and longer)\n' - u'Enter - Save category as %s' % current_cat.title()) + options = (IntegerOption(0, len(supercatlist), 'u'), + IntegerOption(0, len(subcatlist)), + StandardOption('jump to another category', 'j'), + StandardOption('skip this article', 's'), + StandardOption('remove this category tag', 'r'), + context_option, + StandardOption('save category as "{0}"'.format(current_cat.title()), 'c')) + choice = pywikibot.input_choice('Choice:', options, default='c')
- flag = False - while not flag: - pywikibot.output('') - choice = pywikibot.input(u'Choice:') - if choice in ['s', 'S']: - flag = True - elif choice == '': - pywikibot.output(u'Saving category as %s' % current_cat.title()) - if current_cat == original_cat: - pywikibot.output('No changes necessary.') - else: - article.change_category(original_cat, current_cat, - comment=self.editSummary) - flag = True - elif choice in ['j', 'J']: - newCatTitle = pywikibot.input(u'Please enter the category the ' - u'article should be moved to:', - default=None) # require an answer - newCat = pywikibot.Category(pywikibot.Link('Category:' + - newCatTitle)) - # recurse into chosen category - self.move_to_category(article, original_cat, newCat) - flag = True - elif choice in ['r', 'R']: - # remove the category tag - article.change_category(original_cat, None, - comment=self.editSummary) - flag = True - elif choice == '?': - contextLength += 500 - pywikibot.output('\n' + full_text[:contextLength] + '\n') - - # if categories possibly weren't visible, show them additionally - # (maybe this should always be shown?) - if len(full_text) > contextLength: - pywikibot.output('') - pywikibot.output('Original categories: ') - for cat in article.categories(): - pywikibot.output(u'* %s' % cat.title()) - elif choice[0] == 'u': - try: - choice = int(choice[1:]) - except ValueError: - # user pressed an unknown command. Prompt him again. - continue - self.move_to_category(article, original_cat, - supercatlist[choice]) - flag = True + if choice == 'c': + pywikibot.output(u'Saving category as %s' % current_cat.title()) + if current_cat == original_cat: + pywikibot.output('No changes necessary.') else: - try: - choice = int(choice) - except ValueError: - # user pressed an unknown command. Prompt him again. - continue + article.change_category(original_cat, current_cat, + comment=self.editSummary) + elif choice == 'j': + newCatTitle = pywikibot.input(u'Please enter the category the ' + u'article should be moved to:', + default=None) # require an answer + newCat = pywikibot.Category(pywikibot.Link('Category:' + + newCatTitle)) + # recurse into chosen category + self.move_to_category(article, original_cat, newCat) + elif choice == 'r': + # remove the category tag + article.change_category(original_cat, None, + comment=self.editSummary) + elif choice != 's': + if choice[0] == 'u': + # recurse into supercategory + self.move_to_category(article, original_cat, + supercatlist[choice[1]]) + elif choice[0] == '': # recurse into subcategory - self.move_to_category(article, original_cat, subcatlist[choice]) - flag = True + self.move_to_category(article, original_cat, + subcatlist[choice[1]])
def run(self): """Start bot.""" diff --git a/scripts/interwiki.py b/scripts/interwiki.py index fa26860..574e843 100755 --- a/scripts/interwiki.py +++ b/scripts/interwiki.py @@ -359,6 +359,7 @@ import pywikibot
from pywikibot import config, i18n, pagegenerators, textlib, interwiki_graph, titletranslate +from pywikibot.bot import IntegerOption, StandardOption from pywikibot.tools import first_upper
if sys.version_info[0] > 2: @@ -1567,24 +1568,18 @@ pywikibot.output(u" (%d) Found link to %s in:" % (i, page2)) self.whereReport(page2, indent=8) - while True: - # TODO: allow answer to repeat previous or go back after a mistake - answer = pywikibot.input(u"Which variant should be used? (<number>, [n]one, [g]ive up) ").lower() - if answer: - if answer == 'g': - return None - elif answer == 'n': - # None acceptable - break - elif answer.isdigit(): - answer = int(answer) - try: - result[site] = pages[answer - 1] - except IndexError: - # user input is out of range - pass - else: - break + + # TODO: allow answer to repeat previous or go back after a mistake + answer = pywikibot.input_choice( + 'Which variant should be used?', + (IntegerOption(maximum=len(pages) + 1), + StandardOption('none', 'n'), + StandardOption('give up', 'g'))) + if answer == 'g': + return None + elif answer != 'n': + result[site] = pages[answer[1] - 1] + # Loop over the ones that have one solution, so are in principle # not a problem. acceptall = False diff --git a/scripts/solve_disambiguation.py b/scripts/solve_disambiguation.py index 4620a29..03ed627 100755 --- a/scripts/solve_disambiguation.py +++ b/scripts/solve_disambiguation.py @@ -86,9 +86,12 @@
import pywikibot from pywikibot import editor as editarticle -from pywikibot.tools import concat_options, first_lower, first_upper as firstcap +from pywikibot.tools import first_lower, first_upper as firstcap from pywikibot import pagegenerators, config, i18n -from pywikibot.bot import Bot, QuitKeyboardInterrupt +from pywikibot.bot import ( + Bot, QuitKeyboardInterrupt, + StandardOption, HighlightContextOption, ListOption, +)
# Disambiguation Needed template dn_template = { @@ -443,6 +446,89 @@ pass
+class ListAlternativesOption(StandardOption): + + """List the alternatives.""" + + def __init__(self, option, shortcut, bot): + """Constructor.""" + super(ListAlternativesOption, self).__init__(option, shortcut, False) + self._bot = bot + + def result(self, value): + """List the alternatives.""" + self._bot.listAlternatives() + + +class AddAlternativeOption(ListAlternativesOption): + + """Add a new alternative.""" + + def result(self, value): + """Add the alternative and then list them.""" + newAlternative = pywikibot.input(u'New alternative:') + self._bot.alternatives.append(newAlternative) + super(AddAlternativeOption, self).result(value) + + +class EditOption(StandardOption): + + """Edit the text.""" + + def __init__(self, option, shortcut, text, start, title): + """Constructor.""" + super(EditOption, self).__init__(option, shortcut) + self._text = text + self._start = start + self._title = title + + @property + def stop(self): + """Return whether if user didn't press cancel and changed it.""" + return self.new_text and self.new_text != self._text + + def result(self, value): + """Open a text editor and let the user change it.""" + editor = editarticle.TextEditor() + self.new_text = editor.edit(self._text, jumpIndex=self._start(), + highlight=self._title) + return super(EditOption, self).result(value) + + +class ShowPageOption(StandardOption): + + """Show the page's contents in an editor.""" + + def __init__(self, option, shortcut, start, page): + """Constructor.""" + super(ShowPageOption, self).__init__(option, shortcut, False) + self._start = start + if page.isRedirectPage(): + page = page.getRedirectTarget() + self._page = page + + def result(self, value): + """Open a text editor and show the text.""" + editor = editarticle.TextEditor() + editor.edit(self._page.text, + jumpIndex=self._start, + highlight=self._page.title()) + + +class AliasOption(StandardOption): + + """An option allowing multiple aliases which also select it.""" + + def __init__(self, option, shortcuts, stop=True): + """Constructor.""" + super(AliasOption, self).__init__(option, shortcuts[0], stop) + self._aliases = frozenset(s.lower() for s in shortcuts[1:]) + + def test(self, value): + """Test aliases and combine it with the original test.""" + return value.lower() in self._aliases or super(AliasOption, self).test(value) + + class DisambiguationRobot(Bot):
"""Disambiguation bot.""" @@ -651,87 +737,51 @@ len(self.dn_template_str) + 8]): continue
- # This loop will run while the user doesn't choose an option - # that will actually change the page - while True: - self.current_page = refPage + edit = EditOption('edit page', 'e', text, m.start(), disambPage.title()), + context_option = HighlightContextOption( + 'more context', 'm', text, 60, start=m.start(), end=m.end()) + context_option.before_question = True
- if not self.always: - # at the beginning of the link, start red color. - # at the end of the link, reset the color to default - pywikibot.output( - text[max(0, m.start() - context):m.start()] + - '\03{lightred}' + text[m.start():m.end()] + - '\03{default}' + text[m.end():m.end() + context]) - options = ['#', 'r#', '[s]kip link', '[e]dit page', - '[n]ext page', '[u]nlink', '[q]uit'] - if self.dn_template_str: - options.append(u'[t]ag template %s' % self.dn_template_str) - options.append('[m]ore context') - if not edited: - options.append('show [d]isambiguation page') - options += ['[l]ist', '[a]dd new'] - if edited: - options += ['save in this form [x]'] - options = concat_options('Option', 72, options) - choice = pywikibot.input(options) - else: - choice = self.always - if choice in ['a', 'A']: - newAlternative = pywikibot.input(u'New alternative:') - self.alternatives.append(newAlternative) - self.listAlternatives() - elif choice in ['e', 'E']: - editor = editarticle.TextEditor() - newText = editor.edit(text, jumpIndex=m.start(), - highlight=disambPage.title()) - # if user didn't press Cancel - if newText and newText != text: - text = newText - break - elif choice in ['d', 'D']: - editor = editarticle.TextEditor() - if disambPage.isRedirectPage(): - disambredir = disambPage.getRedirectTarget() - editor.edit( - disambredir.get(), - jumpIndex=m.start(), - highlight=disambredir.title()) - else: - editor.edit( - disambPage.get(), - jumpIndex=m.start(), - highlight=disambPage.title()) - elif choice in ['l', 'L']: - self.listAlternatives() - elif choice in ['m', 'M']: - # show more text around the link we're working on - context *= 2 - else: - break + options = [ListOption(self.alternatives, ''), + ListOption(self.alternatives, 'r'), + StandardOption('skip link', 's'), + edit, + StandardOption('next page', 'n'), + StandardOption('unlink', 'u')] + if self.dn_template_str: + # '?', '/' for old choice + options += [AliasOption('tag template %s' % self.dn_template_str, + ['t', '?', '/'])] + options += [context_option] + if not edited: + options += [ShowPageOption('show disambiguation page', 'd', + m.start(), disambPage)] + options += [ListAlternativesOption('list', 'l'), + AddAlternativeOption('add new', 'a')] + if edited: + options += [StandardOption('save in this form', 'x')]
- if choice in ['e', 'E']: - # user has edited the page and then pressed 'OK' + # TODO: Output context on each question + answer = pywikibot.input_choice('Option', options, + default=self.always) + if answer == 'x': + assert edited, 'invalid option before editing' + break + elif answer == 's': + n -= 1 # TODO what's this for? + continue + elif answer == 'e': + text = edit.new_text edited = True curpos = 0 continue - elif choice in ['n', 'N']: + elif answer == 'n': # skip this page if self.primary: # If run with the -primary argument, skip this # occurrence next time. self.primaryIgnoreManager.ignore(refPage) return True - elif choice in ['q', 'Q']: - # quit the program - self.quit() - elif choice in ['s', 'S']: - # Next link on this page - n -= 1 - continue - elif choice in ['x', 'X'] and edited: - # Save the page as is - break
# The link looks like this: # [[page_title|link_text]]trailing_chars @@ -748,8 +798,8 @@ trailing_chars = m.group('linktrail') if trailing_chars: link_text += trailing_chars - # '?', '/' for old choice - if choice in ['t', 'T', '?', '/'] and self.dn_template_str: + if answer == 't': + assert self.dn_template_str # small chunk of text to search search_text = text[m.end():m.end() + context] # figure out where the link (and sentance) ends, put note @@ -765,40 +815,24 @@ text[m.end() + position_split:]) dn = True continue - elif choice in ['u', 'U']: + elif answer == 'u': # unlink - we remove the section if there's any text = text[:m.start()] + link_text + text[m.end():] unlink_counter += 1 continue else: - if len(choice) > 0 and choice[0] == 'r': + # Check that no option from above was missed + assert isinstance(answer, tuple), 'only tuple answer left.' + assert answer[0] in ['r', ''], 'only valid tuple answers.' + if answer[0] == 'r': # we want to throw away the original link text replaceit = link_text == page_title - choice = choice[1:] elif include == "redirect": replaceit = True else: replaceit = False
- try: - choice = int(choice) - except ValueError: - pywikibot.output(u"Unknown option") - # step back to ask the user again what to do with the - # current link - curpos -= 1 - continue - if choice >= len(self.alternatives) or choice < 0: - pywikibot.output( - u"Choice out of range. Please select a number " - u"between 0 and %i." % (len(self.alternatives) - 1)) - # show list of possible choices - self.listAlternatives() - # step back to ask the user again what to do with the - # current link - curpos -= 1 - continue - new_page_title = self.alternatives[choice] + new_page_title = answer[1] repPl = pywikibot.Page(pywikibot.Link(new_page_title, disambPage.site)) if (new_page_title[0].isupper() or
pywikibot-commits@lists.wikimedia.org