jenkins-bot has submitted this change and it was merged.
Change subject: Use consistent Exception for user selecting quit
......................................................................
Use consistent Exception for user selecting quit
Add a QuitKeyboardInterrupt as subclass of KeyboardInterrupt
Add new methods to pywikibot.Bot to:
- quit(): raises QuitKeyboardInterrupt
- run(): if self.generator is defined, call treat() for each page
Split pywikbot.Bot.userPut to create method userConfirm(),
which calls self.quit() if user enters 'q'.
Display edit summary to be used in userPut().
Alter scripts which allowed input of 'q' to quit, subclassing them
to pywikibot.Bot, and using self.userPut() or self.quit() to
cascade the break out of processing pages.
Fixes bug: 68663 misspelling doesnt quit on user input 'q'
Change-Id: I84cdc5dcb8626d2cc1d18f963a8d6fdf4c99da6a
---
M pywikibot/bot.py
M scripts/add_text.py
M scripts/capitalize_redirects.py
M scripts/catall.py
M scripts/clean_sandbox.py
M scripts/commonscat.py
M scripts/cosmetic_changes.py
M scripts/movepages.py
M scripts/redirect.py
M scripts/replace.py
M scripts/solve_disambiguation.py
M scripts/unlink.py
M tests/script_tests.py
13 files changed, 140 insertions(+), 159 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
XZise: Looks good to me, but someone else must approve
jenkins-bot: Verified
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index 41cdb39..b5474eb 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -766,10 +766,25 @@
pywikibot.stdout(globalHelp)
+class QuitKeyboardInterrupt(KeyboardInterrupt):
+
+ """The user has cancelled processing at a prompt."""
+
+
class Bot(object):
"""
- Generic Bot to be subclassed
+ Generic Bot to be subclassed.
+
+ This class provides a run() method for basic processing of a
+ generator one page at a time.
+
+ If the subclass places a page generator in self.generator,
+ Bot will process each page in the generator, invoking the method treat()
+ which must then be implemented by subclasses.
+
+ If the subclass does not set a generator, or does not override
+ treat() or run(), NotImplementedError is raised.
"""
# Bot configuration.
@@ -820,8 +835,30 @@
except KeyError:
raise pywikibot.Error(u'%s is not a valid bot option.' % option)
+ def user_confirm(self, question):
+ """ Obtain user response if bot option 'always' not
enabled. """
+ if self.getOption('always'):
+ return True
+
+ choice = pywikibot.inputChoice(question,
+ ['Yes', 'No', 'All',
'Quit'],
+ ['y', 'N', 'a',
'q'], 'N')
+ if choice == 'n':
+ return False
+
+ if choice == 'q':
+ self.quit()
+
+ if choice == 'a':
+ # Remember the choice
+ self.options['always'] = True
+
+ return True
+
def userPut(self, page, oldtext, newtext, **kwargs):
"""
+ Save a new revision of a page, with user confirmation as required.
+
Print differences, ask user for confirmation,
and puts the page if needed.
@@ -836,25 +873,48 @@
pywikibot.output(u"\n\n>>> \03{lightpurple}%s\03{default}
<<<"
% page.title())
pywikibot.showDiff(oldtext, newtext)
+ if 'comment' in kwargs:
+ pywikibot.output(u'Comment: %s' % kwargs['comment'])
- choice = 'a'
- if not self.getOption('always'):
- choice = pywikibot.inputChoice(
- u'Do you want to accept these changes?',
- ['Yes', 'No', 'All'],
- ['y', 'N', 'a'],
- 'N'
- )
- if choice == 'a':
- # Remember the choice
- self.options['always'] = True
+ if not self.user_confirm('Do you want to accept these changes?'):
+ return
- if 'async' not in kwargs:
- kwargs['async'] = (choice == 'a')
+ if 'async' not in kwargs and self.options['always']:
+ kwargs['async'] = True
- if choice != 'n':
- page.text = newtext
- page.save(**kwargs)
+ page.text = newtext
+ page.save(**kwargs)
+
+ def quit(self):
+ """Cleanup and quit processing."""
+ raise QuitKeyboardInterrupt
+
+ def treat(self, page):
+ """Process one page (Abstract method)."""
+ raise NotImplementedError('Method %s.treat() not implemented.'
+ % self.__class__.__name__)
+
+ def run(self):
+ """Process all pages in generator."""
+ if not hasattr(self, 'generator'):
+ raise NotImplementedError('Variable %s.generator not set.'
+ % self.__class__.__name__)
+ try:
+ for page in self.generator:
+ self.treat(page)
+ except QuitKeyboardInterrupt:
+ pywikibot.output('\nUser quit %s bot run...' %
+ self.__class__.__name__)
+ except KeyboardInterrupt:
+ # TODO: If the ^C occurred during an input()
+ # it should be handled as a QuitKeyboardInterrupt
+ # as developers shouldnt need a backtrace to find
+ # where the input() code is.
+ if config.verbose_output:
+ raise
+ else:
+ pywikibot.output('\nKeyboardInterrupt during %s bot run...' %
+ self.__class__.__name__)
class WikidataBot:
diff --git a/scripts/add_text.py b/scripts/add_text.py
index acbf321..00c9837 100644
--- a/scripts/add_text.py
+++ b/scripts/add_text.py
@@ -118,6 +118,11 @@
def add_text(page, addText, summary=None, regexSkip=None,
regexSkipUrl=None, always=False, up=False, putText=True,
oldTextGiven=None, reorderEnabled=True, create=False):
+ """
+ Add text to a page.
+
+ @rtype: tuple of (text, newtext, always)
+ """
site = page.site
if not summary:
summary = i18n.twtranslate(site, 'add_text-adding',
diff --git a/scripts/capitalize_redirects.py b/scripts/capitalize_redirects.py
index 35906ff..4db3e40 100644
--- a/scripts/capitalize_redirects.py
+++ b/scripts/capitalize_redirects.py
@@ -47,16 +47,10 @@
super(CapitalizeBot, self).__init__(**kwargs)
self.generator = generator
- self.done = False
-
- def run(self):
- for page in self.generator:
- if self.done:
- break
- if page.exists():
- self.treat(page)
def treat(self, page):
+ if not page.exists():
+ return
if page.isRedirectPage():
page = page.getRedirectTarget()
page_t = page.title()
@@ -81,7 +75,7 @@
if choice == 'a':
self.options['always'] = True
elif choice == 'q':
- self.done = True
+ self.quit()
if self.getOption('always') or choice == 'y':
comment = i18n.twtranslate(
page.site,
@@ -114,10 +108,7 @@
if gen:
preloadingGen = pagegenerators.PreloadingGenerator(gen)
bot = CapitalizeBot(preloadingGen, **options)
- try:
- bot.run()
- except KeyboardInterrupt:
- pywikibot.output('\nQuitting program...')
+ bot.run()
else:
pywikibot.showHelp()
diff --git a/scripts/catall.py b/scripts/catall.py
index 780e52d..ccd9f5c 100755
--- a/scripts/catall.py
+++ b/scripts/catall.py
@@ -21,7 +21,6 @@
__version__ = '$Id$'
#
-import sys
import pywikibot
from pywikibot import i18n, textlib
@@ -55,8 +54,7 @@
chosen = None
done = True
elif choice == "q":
- print "quit..."
- sys.exit()
+ raise pywikibot.QuitKeyboardInterrupt
else:
chosen.append(choice)
return chosen
@@ -113,4 +111,7 @@
pywikibot.output(u'%s is a redirect' % p.title())
if __name__ == "__main__":
- main()
+ try:
+ main()
+ except KeyboardInterrupt:
+ pywikibot.output('\nQuitting program...')
diff --git a/scripts/clean_sandbox.py b/scripts/clean_sandbox.py
index dbd1e24..052ec14 100755
--- a/scripts/clean_sandbox.py
+++ b/scripts/clean_sandbox.py
@@ -53,7 +53,7 @@
import datetime
import sys
import pywikibot
-from pywikibot import i18n
+from pywikibot import i18n, Bot
content = {
'commons': u'{{Sandbox}}\n<!-- Please edit only below this line.
-->',
@@ -136,7 +136,7 @@
}
-class SandboxBot(pywikibot.Bot):
+class SandboxBot(Bot):
availableOptions = {
'hours': 1,
'no_repeat': True,
@@ -304,10 +304,7 @@
return
bot = SandboxBot(**opts)
- try:
- bot.run()
- except KeyboardInterrupt:
- pywikibot.output('\nQuitting program...')
+ bot.run()
if __name__ == "__main__":
main()
diff --git a/scripts/commonscat.py b/scripts/commonscat.py
index 082cb9b..6a28ffc 100755
--- a/scripts/commonscat.py
+++ b/scripts/commonscat.py
@@ -65,9 +65,9 @@
import re
-import add_text
+from add_text import add_text
import pywikibot
-from pywikibot import i18n, pagegenerators
+from pywikibot import i18n, pagegenerators, Bot
docuReplacements = {
'¶ms;': pagegenerators.parameterHelp
@@ -234,17 +234,13 @@
}
-class CommonscatBot:
+class CommonscatBot(Bot):
def __init__(self, generator, always, summary=None):
+ super(CommonscatBot, self).__init__(always=always)
self.generator = generator
- self.always = always
self.summary = summary
self.site = pywikibot.Site()
-
- def run(self):
- for page in self.generator:
- self.treat(page)
def treat(self, page):
""" Load the given page, do some changes, and save it.
"""
@@ -262,46 +258,6 @@
% page.title(asLink=True))
else:
self.addCommonscat(page)
-
- def save(self, text, page, comment, minorEdit=True, botflag=True):
- # only save if something was changed
- if text != page.get():
- # Show the title of the page we're working on.
- # Highlight the title in purple.
- pywikibot.output(u"\n\n>>> \03{lightpurple}%s\03{default}
<<<"
- % page.title())
- # show what was changed
- pywikibot.showDiff(page.get(), text)
- pywikibot.output(u'Comment: %s' % comment)
- if not self.always:
- choice = pywikibot.inputChoice(
- u'Do you want to accept these changes?',
- ['Yes', 'No', 'Always', 'Quit'],
- ['y', 'N', 'a', 'q'], 'N')
- if choice == 'a':
- self.always = True
- elif choice == 'q':
- import sys
- sys.exit()
- if self.always or choice == 'y':
- try:
- # Save the page
- page.put(text, comment=comment,
- minorEdit=minorEdit, botflag=botflag)
- except pywikibot.LockedPage:
- pywikibot.output(u"Page %s is locked; skipping."
- % page.title(asLink=True))
- except pywikibot.EditConflict:
- pywikibot.output(
- u'Skipping %s because of edit conflict'
- % (page.title()))
- except pywikibot.SpamfilterError as error:
- pywikibot.output(
- u'Cannot change %s because of spam blacklist entry %s'
- % (page.title(), error.url))
- else:
- return True
- return False
@classmethod
def getCommonscatTemplate(self, code=None):
@@ -382,11 +338,9 @@
else:
textToAdd = u'{{%s|%s}}' % (primaryCommonscat,
commonscatLink)
- (success, status, self.always) = add_text.add_text(page,
- textToAdd,
- self.summary,
- None, None,
- self.always)
+ always = self.getOption('always')
+ rv = add_text(page, textToAdd, self.summary, always=always)
+ self.options['always'] = rv[2]
return True
return True
@@ -423,7 +377,20 @@
msg_change,
fallback=True) % {'oldcat': oldcat,
'newcat': newcat}
- self.save(newtext, page, comment)
+
+ try:
+ self.userPut(page, page.text, newtext, comment=comment)
+ except pywikibot.LockedPage:
+ pywikibot.output(u"Page %s is locked; skipping."
+ % page.title(asLink=True))
+ except pywikibot.EditConflict:
+ pywikibot.output(
+ u'Skipping %s because of edit conflict'
+ % (page.title()))
+ except pywikibot.SpamfilterError as error:
+ pywikibot.output(
+ u'Cannot change %s because of spam blacklist entry %s'
+ % (page.title(), error.url))
def findCommonscatLink(self, page=None):
# In Pywikibot 2.0, page.interwiki() now returns Link objects, not Page objects
diff --git a/scripts/cosmetic_changes.py b/scripts/cosmetic_changes.py
index aad7bf8..aaf3b52 100755
--- a/scripts/cosmetic_changes.py
+++ b/scripts/cosmetic_changes.py
@@ -848,7 +848,6 @@
super(CosmeticChangesBot, self).__init__(**kwargs)
self.generator = generator
- self.done = False
def treat(self, page):
try:
@@ -873,15 +872,6 @@
except pywikibot.EditConflict:
pywikibot.output("An edit conflict has occured at %s."
% page.title(asLink=True))
-
- def run(self):
- try:
- for page in self.generator:
- if self.done:
- break
- self.treat(page)
- except KeyboardInterrupt:
- pywikibot.output('\nQuitting program...')
def main():
diff --git a/scripts/movepages.py b/scripts/movepages.py
index e5329a1..618ac26 100644
--- a/scripts/movepages.py
+++ b/scripts/movepages.py
@@ -41,7 +41,6 @@
__version__ = '$Id$'
#
-import sys
import re
import pywikibot
from pywikibot import i18n, pagegenerators, Bot
@@ -121,7 +120,7 @@
self.options['always'] = True
self.moveOne(page, newPageTitle)
elif choice2 == 'q':
- sys.exit()
+ self.quit()
elif choice2 == 'n':
pass
else:
@@ -161,7 +160,7 @@
self.appendAll = True
self.moveOne(page, newPageTitle)
elif choice2 == 'q':
- sys.exit()
+ self.quit()
elif choice2 == 'n':
pass
else:
@@ -195,7 +194,7 @@
self.regexAll = True
self.moveOne(page, newPageTitle)
elif choice2 == 'q':
- sys.exit()
+ self.quit()
elif choice2 == 'n':
pass
else:
@@ -203,13 +202,9 @@
elif choice == 'n':
pass
elif choice == 'q':
- sys.exit()
+ self.quit()
else:
self.treat(page)
-
- def run(self):
- for page in self.generator:
- self.treat(page)
def main():
diff --git a/scripts/redirect.py b/scripts/redirect.py
index 454d1f3..72e64bb 100755
--- a/scripts/redirect.py
+++ b/scripts/redirect.py
@@ -379,20 +379,6 @@
self.exiting = False
self._valid_template = None
- def prompt(self, question):
- if not self.getOption('always'):
- choice = pywikibot.inputChoice(question,
- ['Yes', 'No', 'All',
'Quit'],
- ['y', 'N', 'a',
'q'], 'N')
- if choice == 'n':
- return False
- elif choice == 'q':
- self.exiting = True
- return False
- elif choice == 'a':
- self.options['always'] = True
- return True
-
def has_valid_template(self, twtitle):
"""Check whether a template from
translatewiki.net does exist on
real
wiki. We assume we are always working on self.site
@@ -415,8 +401,6 @@
# get reason for deletion text
for redir_name in self.generator.retrieve_broken_redirects():
self.delete_1_broken_redirect(redir_name)
- if self.exiting:
- break
def moved_page(self, source):
gen = iter(self.site.logevents(logtype='move', page=source, total=1))
@@ -471,7 +455,7 @@
content)
pywikibot.showDiff(content, text)
pywikibot.output(u'Summary - %s' % reason)
- if self.prompt(
+ if self.user_confirm(
u'Redirect target %s has been moved to %s.\n'
u'Do you want to fix %s?'
% (targetPage, movedTarget, redir_page)):
@@ -487,7 +471,7 @@
pywikibot.output(u'%s is locked.'
% redir_page.title())
pass
- elif self.getOption('delete') and self.prompt(
+ elif self.getOption('delete') and self.user_confirm(
u'Redirect target %s does not exist.\n'
u'Do you want to delete %s?'
% (targetPage.title(asLink=True),
@@ -532,8 +516,6 @@
def fix_double_redirects(self):
for redir_name in self.generator.retrieve_double_redirects():
self.fix_1_double_redirect(redir_name)
- if self.exiting:
- break
def fix_1_double_redirect(self, redir_name):
redir = pywikibot.Page(self.site, redir_name)
@@ -677,7 +659,7 @@
{'to': targetPage.title(asLink=True)}
)
pywikibot.showDiff(oldText, text)
- if self.prompt(u'Do you want to accept the changes?'):
+ if self.user_confirm(u'Do you want to accept the changes?'):
try:
redir.put(text, summary)
except pywikibot.LockedPage:
@@ -713,10 +695,11 @@
else:
self.fix_1_double_redirect(redir_name)
count += 1
- if self.exiting or (self.getOption('number') and count >=
self.getOption('number')):
+ if self.getOption('number') and count >=
self.getOption('number'):
break
def run(self):
+ """Run the script method selected by 'action'
parameter."""
# TODO: make all generators return a redirect type indicator,
# thus make them usable with 'both'
if self.action == 'double':
diff --git a/scripts/replace.py b/scripts/replace.py
index c88dcdc..adb31ce 100755
--- a/scripts/replace.py
+++ b/scripts/replace.py
@@ -126,9 +126,8 @@
import re
import time
import pywikibot
-from pywikibot import pagegenerators
+from pywikibot import i18n, textlib, pagegenerators, Bot
from pywikibot import editor as editarticle
-from pywikibot import i18n, textlib
import webbrowser
# Imports predefined replacements tasks from fixes.py
@@ -219,7 +218,7 @@
return False
-class ReplaceRobot:
+class ReplaceRobot(Bot):
"""
A bot that can do text replacements.
"""
@@ -399,7 +398,7 @@
new_text = original_text
continue
if choice == 'q':
- return
+ self.quit()
if choice == 'a':
self.acceptall = True
if choice == 'y':
diff --git a/scripts/solve_disambiguation.py b/scripts/solve_disambiguation.py
index 92d9d53..1d15922 100644
--- a/scripts/solve_disambiguation.py
+++ b/scripts/solve_disambiguation.py
@@ -79,14 +79,12 @@
#
import re
-import sys
import codecs
import pywikibot
from pywikibot import editor as editarticle
-from pywikibot import pagegenerators
-from pywikibot import config
-from pywikibot import i18n
+from pywikibot import pagegenerators, config, i18n
+from pywikibot.bot import Bot, QuitKeyboardInterrupt
# Disambiguation Needed template
dn_template = {
@@ -437,7 +435,7 @@
pass
-class DisambiguationRobot(object):
+class DisambiguationRobot(Bot):
ignore_contents = {
'de': (u'{{[Ii]nuse}}',
@@ -714,7 +712,7 @@
return True
elif choice in ['q', 'Q']:
# quit the program
- return False
+ self.quit()
elif choice in ['s', 'S']:
# Next link on this page
n -= 1
@@ -876,7 +874,7 @@
Please enter the name of the page where the redirect should have pointed at,
or press enter to quit:""")
if user_input == "":
- sys.exit(1)
+ self.quit()
else:
self.alternatives.append(user_input)
except pywikibot.IsNotRedirectPage:
@@ -1001,9 +999,12 @@
preloadingGen = pagegenerators.PreloadingGenerator(gen)
for refPage in preloadingGen:
if not self.primaryIgnoreManager.isIgnored(refPage):
- # run until the user selected 'quit'
- if not self.treat(refPage, disambPage):
- break
+ try:
+ self.treat(refPage, disambPage)
+ except QuitKeyboardInterrupt:
+ pywikibot.output('\nUser quit %s bot run...' %
+ self.__class__.__name__)
+ return
# clear alternatives before working on next disambiguation page
self.alternatives = []
diff --git a/scripts/unlink.py b/scripts/unlink.py
index b6b5d09..862cd36 100755
--- a/scripts/unlink.py
+++ b/scripts/unlink.py
@@ -68,7 +68,6 @@
# note that the definition of 'letter' varies from language to language.
self.linkR =
re.compile(r'\[\[(?P<title>[^\]\|#]*)(?P<section>#[^\]\|]*)?(\|(?P<label>[^\]]*))?\]\](?P<linktrail>%s)'
% linktrail)
- self.done = False
self.comment = i18n.twtranslate(self.pageToUnlink.site,
'unlink-unlinking',
self.pageToUnlink.title())
@@ -128,8 +127,7 @@
elif choice == 'a':
self.options['always'] = True
elif choice == 'q':
- self.done = True
- return text, False
+ self.quit()
new = match.group('label') or match.group('title')
new += match.group('linktrail')
return text[:match.start()] + new + text[match.end():], False
@@ -167,12 +165,6 @@
% page.title(asLink=True))
except pywikibot.LockedPage:
pywikibot.output(u"Page %s is locked?!" % page.title(asLink=True))
-
- def run(self):
- for page in self.generator:
- if self.done:
- break
- self.treat(page)
def main():
diff --git a/tests/script_tests.py b/tests/script_tests.py
index 79e7f27..7e0100d 100644
--- a/tests/script_tests.py
+++ b/tests/script_tests.py
@@ -65,7 +65,7 @@
'catall': 'q\n', # q for quit
'editarticle': 'Test page\n',
'interwiki': 'Test page\n',
- # 'misspelling': 'q\n', # pressing 'q' doesnt work. bug
68663
+ 'misspelling': 'q\n',
'pagefromfile': 'q\n',
'replace': 'foo\nbar\n\n\n', # match, replacement,
# Enter to begin, Enter for default summary.
--
To view, visit
https://gerrit.wikimedia.org/r/139309
To unsubscribe, visit
https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I84cdc5dcb8626d2cc1d18f963a8d6fdf4c99da6a
Gerrit-PatchSet: 24
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Guoguo12 <Guoguo12(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: Nullzero <nullzero.free(a)gmail.com>
Gerrit-Reviewer: Ricordisamoa <ricordisamoa(a)openmailbox.org>
Gerrit-Reviewer: Russell Blau <russblau(a)imapmail.org>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot <>