Merlijn van Deen has uploaded a new change for review.
https://gerrit.wikimedia.org/r/233210
Change subject: implement site-based configuration
......................................................................
implement site-based configuration
Change-Id: Id9c96d0bdbbe9b539f911d01b23a922da9415a9b
Original-Change-Id: Ie9c86c196ef33ff7bb336bb2bac9e688b41938a3
---
M pywikibot/bot.py
M scripts/lonelypages.py
2 files changed, 187 insertions(+), 39 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/pywikibot/core
refs/changes/10/233210/1
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index a110ea1..22a2852 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -37,6 +37,11 @@
VERBOSE = 18
INPUT = 25
+try:
+ import mwparserfromhell
+except ImportError as e:
+ mwparserfromhell = e
+
import pywikibot
from pywikibot import backports
@@ -1318,6 +1323,56 @@
i18n.input('pywikibot-enter-finished-browser')
+def load_settings(site, page_title):
+ """
+ Load the page from the site and parse it as JSON.
+
+ The JSON data must be a dictionary which is read from a page in the
+ MediaWiki namespace and from the User namespace. The User settings
overwrite
+ the MediaWiki settings.
+
+ It searches for the page with JSON as a extension and without it. If the
+ extension is not JSON it'll search for <pre> and <syntaxhighlight> tags in
+ the text and just interpret the content. If the tag is <syntaxhighlight>
+ it'll skip those where the lang is set to something else than javascript.
+
+ @return: The combined result or None if no page exist.
+ """
+ settings = None
+ titles = [(page_title, 8)]
+ if site.user():
+ titles += [(site.user() + '/' + page_title, 2)]
+ for title, namespace in titles:
+ page = pywikibot.Page(site, title + '.json', namespace)
+ if page.exists():
+ if not settings:
+ settings = {}
+ settings.update(json.loads(page.text))
+ else:
+ page = pywikibot.Page(site, title, namespace)
+ if page.exists():
+ # parse text from normal pages to allow for comments inside
+ if isinstance(mwparserfromhell, ImportError):
+ raise ImportError('To parse normal non-JSON pages '
+ 'mwparserfromhell needs to be
installed.')
+ parsed = mwparserfromhell.parse(page.text)
+ for tag in parsed.ifilter_tags():
+ if tag.tag == 'syntaxhighlight':
+ lang = 'javascript'
+ for attr in tag.attributes:
+ if attr.name == 'lang':
+ lang = attr.value
+ elif tag.tag == 'pre':
+ lang = 'javascript'
+ else:
+ continue
+
+ if lang == 'javascript':
+ settings.update(json.loads(tag.content))
+
+ return settings
+
+
class BaseBot(object):
"""
@@ -1857,6 +1912,110 @@
**kwargs)
+class SettingsBaseBot(BaseBot):
+
+ """
+ A bot querying and caching the settings for each site.
+
+ It must define 'settings_page' which is the page title without namespace
and
+ file extension. This page must either exist in the MediaWiki or User
+ namespace. Whenever the script requests the settings it returns the
combined
+ result of it using C{load_settings}. The settings_page is case sensitive
+ in the User namespace (when it's a subpage) but the first letter is case
+ insensitive in the MediaWiki namespace.
+
+ The available settings are defined via 'mandatory_settings' and
+ 'optional_settings' which may not be changed after using the 'settings'
+ property. Each can be either an iterable or None. If either of them is None
+ it accepts and loads all available names. Otherwise it only loads entries
+ whose name are in either of the lists. All names from 'mandatory_settings'
+ must be present.
+
+ When the script's docstring has a '&settings;' entry it'll automatically
+ add a note about all classes using SettingsBot in that module.
+ """
+
+ settings_page = None
+ mandatory_settings = None
+ optional_settings = []
+
+ def __init__(self, *args, **kwargs):
+ """Constructor."""
+ super(SettingsBot, self).__init__(*args, **kwargs)
+ # the cached settings for each site
+ self._settings = {}
+
+ def _parse_settings(self, settings):
+ """Parse the settings dict and remove unrecognized values."""
+ def freeze_names(prop_name):
+ names = getattr(self, prop_name)
+ if names is not None:
+ names = frozenset(names)
+ if not hasattr(self, '_{0}'.format(prop_name)):
+ setattr(self, '_{0}'.format(prop_name), names)
+ elif names != set(getattr(self, '_{0}'.format(prop_name))):
+ raise ValueError('Settings name "{0}" changed since last '
+ 'request'.format(prop_name))
+ freeze_names('mandatory_settings')
+ freeze_names('optional_settings')
+
+ if (self._mandatory_settings is not None and
+ self._optional_settings is not None):
+ unrecognized = (set(settings) - self._mandatory_settings -
+ self._optional_settings)
+ settings = dict(setting for setting in settings.items()
+ if setting[0] not in unrecognized)
+ else:
+ unrecognized = set()
+ if self._mandatory_settings:
+ missing = self._mandatory_settings - set(settings)
+ if missing:
+ raise ValueError('The values for "{0}" are/is missing.'.format(
+ '", "'.join(missing)))
+ if unrecognized:
+ # might be harmless (e.g. because the page supports deprecated
+ # parameters or because the script uses deprecated parameters)
+ debug('Found unrecognized setting keys "{0}"'.format(
+ '", "'.join(unrecognized)), _logger)
+ return settings
+
+ def get_settings(self, site):
+ """Query the settings for the current site."""
+ if not self.settings_page:
+ raise Exception('The "settings_page" for {0} is not '
+ 'defined.'.format(self.__class__.__name__))
+ if site not in self._settings:
+ settings = load_settings(site, self.settings_page)
+ self._settings[site] = self._parse_settings(settings)
+ return self._settings[site]
+
+
+class SettingsBot(CurrentPageBot):
+
+ """A settings bot using the current page's site."""
+
+ @property
+ def settings(self):
+ """Query the settings for the current page's site."""
+ return self.get_settings(self.current_page.site)
+
+
+class SettingsCallbackBot(SettingsBaseBot):
+
+ """A class calling settings_callback on the parsed settings."""
+
+ settings_callback = None
+
+ def _parse_settings(self, settings):
+ """Use settings_callback with the parsed settings."""
+ if not self.settings_callback:
+ raise Exception('The "settings_callback" for {0} is not '
+ 'defined.'.format(self.__class__.__name__))
+ return self.settings_callback(
+ self.site,
+ **super(SettingsCallbackBot, self)._parse_settings(settings))
+
+
class AutomaticTWSummaryBot(CurrentPageBot):
"""
diff --git a/scripts/lonelypages.py b/scripts/lonelypages.py
index eb54b3e..681eec2 100755
--- a/scripts/lonelypages.py
+++ b/scripts/lonelypages.py
@@ -26,6 +26,7 @@
-always Always say yes, won't ask
+&settings;
--- Examples ---
python lonelypages.py -enable:User:Bot/CheckBot -always
@@ -46,7 +47,8 @@
import sys
import pywikibot
-from pywikibot import i18n, pagegenerators, Bot
+from pywikibot import i18n, pagegenerators
+from pywikibot.bot import NoRedirectPageBot, SettingsBot, SettingsCallbackBot
# This is required for the text that is shown when you run this script
# with the parameter -help.
@@ -81,7 +83,7 @@
"""The orphan template configuration."""
- def __init__(self, name, parameters, aliases=None, subst=False):
+ def __init__(self, site, name, parameters, aliases=None, subst=False):
self._name = name
if not aliases:
aliases = []
@@ -91,39 +93,33 @@
name = 'subst:' + name
if parameters:
name += '|' + parameters
- self._template = u'{{' + name + '}}'
+ self.template = '{{' + name + '}}'
self._names = frozenset(aliases)
+ self._site = site
- def generate(self, site):
- """Create regex from the given names."""
template_ns = site.namespaces[10]
# TODO: Add redirects to self.names too
if not pywikibot.Page(site, self._name, template_ns.id).exists():
- raise ValueError(u'Orphan template "{0}" does not exist on '
- u'"{1}".'.format(self._name, site))
+ raise ValueError('Orphan template "{0}" does not exist on '
+ '"{1}".'.format(self._name, site))
for name in self._names:
if not pywikibot.Page(site, name, template_ns.id).exists():
- pywikibot.warning(u'Orphan template alias "{0}" does not exist
'
- u'on "{1}"'.format(name, site))
- return re.compile(r'\{\{(?:' + u':|'.join(template_ns) + '|)(' +
- u'|'.join(re.escape(name) for name in self._names) +
- r')[\|\}]', re.I)
+ pywikibot.warning('Orphan template alias "{0}" does not exist '
+ 'on "{1}"'.format(name, site))
+ self.regex = re.compile(
+ r'\{\{(?:' + ':|'.join(template_ns) + '|)(' +
+ '|'.join(re.escape(name) for name in self._names) +
+ r')[\|\}]', re.I)
-# The orphan template names in the different languages.
-templates = {
- 'ar': OrphanTemplate(u'ﻲﺘﻴﻣﺓ', u'ﺕﺍﺮﻴﺧ={{ﻦﺴﺧ:ﺎﺴﻣ_ﺶﻫﺭ}} {{ﻦﺴﺧ:ﻉﺎﻣ}}'),
- 'ca': OrphanTemplate('Orfe', 'date={{subst:CURRENTMONTHNAME}}
{{subst:CURRENTYEAR}}'),
- 'en': OrphanTemplate('Orphan', 'date={{subst:CURRENTMONTHNAME}}
{{subst:CURRENTYEAR}}', ['wi']),
- 'it': OrphanTemplate('O', '||mese={{subst:CURRENTMONTHNAME}}
{{subst:CURRENTYEAR}}', ['a']),
- 'ja': OrphanTemplate(u'孤立', '{{subst:DATE}}'),
- 'zh': OrphanTemplate(u'Orphan/auto', '', ['orphan'], True),
-}
-
-
-class LonelyPagesBot(Bot):
+class LonelyPagesBot(NoRedirectPageBot, SettingsBot, SettingsCallbackBot):
"""Orphan page tagging bot."""
+
+ settings_page = 'pywikibot-lonelypages'
+ mandatory_settings = ['name', 'parameters']
+ optional_settings = ['aliases', 'subst']
+ settings_callback = OrphanTemplate
def __init__(self, generator, **kwargs):
self.availableOptions.update({
@@ -142,19 +138,14 @@
self.site, 'lonelypages-comment-add-template')
self.commentdisambig = i18n.twtranslate(
self.site, 'lonelypages-comment-add-disambig-template')
- orphan_template = i18n.translate(self.site, templates)
- if orphan_template is None:
- pywikibot.showHelp()
- sys.exit(u'Missing configuration for site %s' % self.site)
try:
- self._exception = orphan_template.generate(self.site)
+ if self.settings is None:
+ pywikibot.showHelp()
+ sys.exit(u'Missing configuration for site %s' % self.site)
except ValueError as e:
pywikibot.showHelp()
pywikibot.error(e)
sys.exit(u'Missing configuration for site %s' % self.site)
- else:
- self.exception = [self._exception.pattern] # for backwards
compatibility
- self.template = orphan_template._template
# DisambigPage part
if self.getOption('disambigPage') is not None:
self.disambigpage = pywikibot.Page(self.site,
self.getOption('disambigPage'))
@@ -192,11 +183,9 @@
return
super(LonelyPagesBot, self).run()
- def treat(self, page):
- pywikibot.output(u"Checking %s..." % page.title())
- if page.isRedirectPage(): # If redirect, skip!
- pywikibot.output(u'%s is a redirect! Skip...' % page.title())
- return
+ def treat_page(self):
+ """Search for the orphan template and add it if necessary."""
+ page = self.current_page
refs = list(page.getReferences(total=1))
if len(refs) > 0:
pywikibot.output(u"%s isn't orphan! Skip..." % page.title())
@@ -211,7 +200,7 @@
except pywikibot.IsRedirectPage:
pywikibot.output(u"%s is a redirect! Skip..." % page.title())
return
- if self._exception.search(oldtxt):
+ if self.settings.regex.search(oldtxt):
pywikibot.output(
u'Your regex has found something in %s, skipping...'
% page.title())
@@ -232,7 +221,7 @@
else:
# Ok, the page need the template. Let's put it there!
# Adding the template in the text
- newtxt = u"%s\n%s" % (self.template, oldtxt)
+ newtxt = '%s\n%s' % (self.settings.template, oldtxt)
self.userPut(page, oldtxt, newtxt, summary=self.comment)
--
To view, visit https://gerrit.wikimedia.org/r/233210
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Id9c96d0bdbbe9b539f911d01b23a922da9415a9b
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Merlijn van Deen <[email protected]>
Gerrit-Reviewer: XZise <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits