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

Reply via email to