XZise has uploaded a new change for review.
https://gerrit.wikimedia.org/r/186638
Change subject: [FEAT] Configuration classes
......................................................................
[FEAT] Configuration classes
This adds configuration classes which allow to lazy load the
configuration values and thus wait on bot.handle_args to set a directory
if one was choosen explicitly via the command line.
Change-Id: I4214ec5dd40308f7fc3fa87126175393f1edaf0f
---
M generate_user_files.py
M pywikibot/bot.py
M pywikibot/config2.py
A pywikibot/configuration.py
4 files changed, 989 insertions(+), 881 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/pywikibot/core
refs/changes/38/186638/1
diff --git a/generate_user_files.py b/generate_user_files.py
index a1c83dc..fba8f1f 100644
--- a/generate_user_files.py
+++ b/generate_user_files.py
@@ -86,7 +86,7 @@
pywikibot.output("Created new directory.")
break
- if new_base == pywikibot.config2.get_base_dir(new_base):
+ if new_base == pywikibot.config2.get_base_dir(None, new_base):
# config would find that file
return new_base
from textwrap import wrap
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index e814aef..635dfa4 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -33,6 +33,7 @@
import pywikibot
from pywikibot import config
+from pywikibot import configuration
from pywikibot import version
from pywikibot.tools import deprecated
@@ -644,6 +645,10 @@
nonGlobalArgs = []
username = None
do_help = None if do_help else False
+ args, config_dir = configuration._parse_arg(args)
+
+ config._load(config_dir)
+
for arg in args:
if do_help is not False and arg == '-help':
do_help = True
@@ -736,6 +741,8 @@
init_handlers()
if config.verbose_output:
+ # TODO: Output config.base_dir
+
# Please don't change the regular expression here unless you really
# have to - some git versions (like 1.7.0.4) seem to treat lines
# containing just `$Id:` as if they were ident lines (see
diff --git a/pywikibot/config2.py b/pywikibot/config2.py
index 6fea03a..adbdb4b 100644
--- a/pywikibot/config2.py
+++ b/pywikibot/config2.py
@@ -22,123 +22,9 @@
__version__ = '$Id$'
#
-import os
+import types
import sys
-import collections
-# Please keep _imported_modules in sync with the imports above
-_imported_modules = ('os', 'sys', 'collections')
-
-# IMPORTANT:
-# Do not change any of the variables in this file. Instead, make
-# a file user-config.py, and overwrite values in there.
-
-# Note: all variables defined in this module are made available to bots as
-# configuration settings, *except* variable names beginning with an
-# underscore (example: _variable). Be sure to use an underscore on any
-# variables that are intended only for internal use and not to be exported
-# to other modules.
-
-_private_values = ['authenticate', 'proxy', 'db_password']
-_deprecated_variables = ['use_SSL_onlogin', 'use_SSL_always',
- 'available_ssl_project_comment']
-
-# ############# ACCOUNT SETTINGS ##############
-
-# The family of sites we are working on. pywikibot will import
-# families/xxx_family.py so if you want to change this variable,
-# you need to write such a file if one does not exist.
-family = 'wikipedia'
-# The language code of the site we're working on.
-mylang = 'language'
-# If family and mylang are not modified from the above, the default is changed
-# to test:test, which is test.wikipedia.org, at the end of this module.
-
-# The dictionary usernames should contain a username for each site where you
-# have a bot account. Please set your usernames by adding such lines to your
-# user-config.py:
-#
-# usernames['wikipedia']['de'] = 'myGermanUsername'
-# usernames['wiktionary']['en'] = 'myEnglishUsername'
-#
-# If you have a unique username for all languages of a family,
-# you can use '*'
-# usernames['wikibooks']['*'] = 'mySingleUsername'
-#
-# If you have a sysop account on some wikis, this will be used to delete pages
-# or to edit locked pages if you add such lines to your
-# user-config.py:
-#
-# sysopnames['wikipedia']['de'] = 'myGermanUsername'
-# sysopnames['wiktionary']['en'] = 'myEnglishUsername'
-#
-# If you have a unique syop account for all languages of a family,
-# you can use '*'
-# sysopnames['myownwiki']['*'] = 'mySingleUsername'
-usernames = collections.defaultdict(dict)
-sysopnames = collections.defaultdict(dict)
-disambiguation_comment = collections.defaultdict(dict)
-
-# User agent format.
-# For the meaning and more help in customization see:
-# https://www.mediawiki.org/wiki/Manual:Pywikibot/User-agent
-user_agent_format = '{script_product} ({script_comments}) {pwb} ({revision})
{httplib2} {python}'
-
-# The default interface for communicating with the site
-# currently the only defined interface is 'APISite', so don't change this!
-site_interface = 'APISite'
-# number of days to cache namespaces, api configuration, etc.
-API_config_expiry = 30
-
-# The maximum number of bytes which uses a GET request, if not positive
-# it'll always use POST requests
-maximum_GET_length = 255
-# Some networks modify GET requests when they are not encrypted, to avoid
-# bug reports related to that disable those. If we are confident that bug
-# related to this are really because of the network this could be changed.
-enable_GET_without_SSL = False
-
-# Solve captchas in the webbrowser. Setting this to False will result in the
-# exception CaptchaError being thrown if a captcha is encountered.
-solve_captcha = True
-
-# Some sites will require password authentication to access the HTML pages at
-# the site. If you have any such site, add lines to your user-config.py of
-# the following form:
-#
-# authenticate['en.wikipedia.org'] = ('John','XXXXX')
-#
-# where John is your login name, and XXXXX your password.
-# Note:
-# 1. This is only for sites that use authentication in the form that gives
-# you a popup for name and password when you try to access any data, NOT
-# for, for example, wiki usernames
-# 2. You must use the hostname of the site, not its family/language pair
-authenticate = {}
-
-#
-# Secure connection overrides
-#
-# These settings are deprecated. They existed to support the Wikimedia
-# family which only served HTTPS on https://secure.wikimedia.org/<site>/<uri>
-# Use Family.protocol()
-use_SSL_onlogin = False # if available, use SSL when logging in
-use_SSL_always = False # if available, use SSL for all API queries
-# Available secure projects should be listed here.
-available_ssl_project = []
-
-# By default you are asked for a password on the terminal.
-# A password file may be used. e.g. password_file = ".passwd"
-# The password file should consist of lines containing
-# Python tuples of any of the following formats:
-# (code, family, username, password)
-# (family, username, password)
-# (username, password)
-password_file = None
-
-# edit summary to use if not supplied by bot script
-# WARNING: this should NEVER be used in practice, ALWAYS supply a more
-# relevant summary for bot edits
-default_edit_summary = u'Pywikibot v.2'
+import pywikibot.configuration
def get_base_dir(test_directory=None):
@@ -165,778 +51,39 @@
@type test_directory: str or None
@rtype: unicode
"""
- def exists(directory):
- directory = os.path.abspath(directory)
- if directory == test_directory:
- return True
- else:
- return os.path.exists(os.path.join(directory, 'user-config.py'))
+ args, arg_path = pywikibot.configuration._parse_arg(sys.argv[1:])
+ sys.argv[:] = [sys.argv[0]] + args
+ return pywikibot.configuration.get_base_dir(arg_path, test_directory)
- if test_directory is not None:
- test_directory = os.path.abspath(test_directory)
- DIRNAME_WIN = u"Pywikibot"
- DIRNAME_WIN_FBCK = u"pywikibot"
- DIRNAME_UNIX = u".pywikibot"
+class _ConfigDeprecationWrapper(types.ModuleType):
- base_dir = ""
- for arg in sys.argv[1:]:
- if arg.startswith("-dir:"):
- base_dir = arg[5:]
- base_dir = os.path.expanduser(base_dir)
- break
- else:
- if ('PYWIKIBOT2_DIR' in os.environ and
- exists(os.path.abspath(os.environ['PYWIKIBOT2_DIR']))):
- base_dir = os.path.abspath(os.environ['PYWIKIBOT2_DIR'])
- elif exists('.'):
- base_dir = os.path.abspath('.')
- elif ('PYWIKIBOT2_DIR_PWB' in os.environ and
- exists(os.path.abspath(os.environ['PYWIKIBOT2_DIR_PWB']))):
- base_dir = os.path.abspath(os.environ['PYWIKIBOT2_DIR_PWB'])
- else:
- base_dir_cand = []
- home = os.path.expanduser("~")
- if sys.platform == 'win32':
- import platform
- win_version = int(platform.version()[0])
- if win_version == 5:
- sub_dir = ["Application Data"]
- elif win_version == 6:
- sub_dir = ["AppData", "Roaming"]
- else:
- raise WindowsError(u'Windows version %s not supported yet.'
- % win_version)
- base_dir_cand.extend([[home] + sub_dir + [DIRNAME_WIN],
- [home] + sub_dir + [DIRNAME_WIN_FBCK]])
- else:
- base_dir_cand.append([home, DIRNAME_UNIX])
+ """A wrapper for a module to deprecate classes or variables of it."""
- for dir in base_dir_cand:
- dir = os.path.join(*dir)
- if not os.path.isdir(dir):
- os.makedirs(dir, mode=0o700)
- if exists(dir):
- base_dir = dir
- break
+ def __init__(self):
+ """
+ Initialise the wrapper.
- if not os.path.isabs(base_dir):
- base_dir = os.path.normpath(os.path.join(os.getcwd(), base_dir))
- # make sure this path is valid and that it contains user-config file
- if not os.path.isdir(base_dir):
- raise RuntimeError("Directory '%s' does not exist." % base_dir)
- # check if user-config.py is in base_dir
- if not exists(base_dir):
- exc_text = "No user-config.py found in directory '%s'.\n" % base_dir
- if os.environ.get('PYWIKIBOT2_NO_USER_CONFIG', '0') == '1':
- print(exc_text)
- else:
- exc_text += " Please check that user-config.py is stored in the
correct location.\n"
- exc_text += " Directory where user-config.py is searched is
determined as follows:\n\n"
- exc_text += " " + get_base_dir.__doc__
- raise RuntimeError(exc_text)
+ It will automatically overwrite the module with this instance in
+ C{sys.modules}.
- return base_dir
+ @param module: The module name or instance
+ @type module: str or module
+ """
+ module = sys.modules['pywikibot.config2']
+ super(_ConfigDeprecationWrapper, self).__setattr__('_configuration',
pywikibot.configuration)
+ super(_ConfigDeprecationWrapper, self).__setattr__('__doc__',
module.__doc__)
-_get_base_dir = get_base_dir # for backward compatibility
-_base_dir = get_base_dir()
-# Save base_dir for use by other modules
-base_dir = _base_dir
+ if __debug__:
+ sys.modules[module.__name__] = self
-for arg in sys.argv[1:]:
- if arg.startswith("-verbose") or arg == "-v":
- print("The base directory is %s" % base_dir)
- break
-family_files = {}
+ if __name__ == '__main__':
+ self._configuration(*sys.argv)
+ def __setattr__(self, name, value):
+ setattr(self._configuration.configuration, name, value)
-def register_family_file(family_name, file_path):
- """Register a single family class file."""
- usernames[family_name] = {}
- sysopnames[family_name] = {}
- disambiguation_comment[family_name] = {}
- family_files[family_name] = file_path
+ def __getattr__(self, name):
+ return getattr(self._configuration.configuration, name)
-
-def register_families_folder(folder_path):
- """Register all family class files contained in a directory."""
- for file_name in os.listdir(folder_path):
- if file_name.endswith("_family.py"):
- family_name = file_name[:-len("_family.py")]
- register_family_file(family_name, os.path.join(folder_path,
file_name))
-
-
-# Get the names of all known families, and initialize with empty dictionaries.
-# ‘families/’ is a subdirectory of the directory in which config2.py is found.
-register_families_folder(os.path.join(os.path.dirname(__file__), 'families'))
-register_family_file('wikiapiary', 'https://wikiapiary.com')
-
-# Set to True to override the {{bots}} exclusion protocol (at your own risk!)
-ignore_bot_templates = False
-
-# ############# USER INTERFACE SETTINGS ##############
-
-# The encoding that's used in the user's console, i.e. how strings are encoded
-# when they are read by raw_input(). On Windows systems' DOS box, this should
-# be 'cp850' ('cp437' for older versions). Linux users might try 'iso-8859-1'
-# or 'utf-8'.
-# This default code should work fine, so you don't have to think about it.
-# TODO: consider getting rid of this config variable.
-try:
- console_encoding = sys.stdout.encoding
-except:
- # When using pywikibot inside a daemonized twisted application,
- # we get "StdioOnnaStick instance has no attribute 'encoding'"
- console_encoding = None
-
-# The encoding the user would like to see text transliterated to. This can be
-# set to a charset (e.g. 'ascii', 'iso-8859-1' or 'cp850'), and we will output
-# only characters that exist in that charset. However, the characters will be
-# output using console_encoding.
-# If this is not defined on Windows, we emit a Warning explaining the user
-# to either switch to a Unicode-able font and use
-# transliteration_target = None
-# or to keep using raster fonts and set
-# transliteration_target = console_encoding
-# After emitting the warning, this last option will be set.
-
-transliteration_target = 'not set'
-
-# The encoding in which textfiles are stored, which contain lists of page
-# titles. The most used is: 'utf-8'. 'utf-8-sig' recognizes BOM but it is
-# available on Python 2.5 or higher. For a complete list please see:
-# https://docs.python.org/2/library/codecs.html#standard-encodings
-textfile_encoding = 'utf-8'
-
-# tkinter isn't yet ready
-userinterface = 'terminal'
-
-# this can be used to pass variables to the UI init function
-# useful for e.g.
-# userinterface_init_kwargs = {'default_stream': 'stdout'}
-userinterface_init_kwargs = {}
-
-# i18n setting for user interface language
-# default is config.mylang or 'en'
-userinterface_lang = None
-
-# Should we transliterate characters that do not exist in the console
-# character set?
-# True: whenever possible
-# False: never - always replace them by question marks
-# Currently only works if interface 'terminal' is set.
-transliterate = True
-
-# Should the system bell ring if the bot expects user input?
-ring_bell = False
-
-# Colorization can be used to markup important text parts of the output.
-# On Linux/Unix terminals, ANSI escape codes are used for this. On Windows,
-# it is done by a DLL call via ctypes. ctypes is only available since
-# Python 2.5, so if you're using Python 2.4 or lower on Windows, you should
-# upgrade.
-# Set this to False if you're using Linux and your tty doesn't support
-# ANSI colors.
-try:
- # Don't print colorized when the output is, for example, piped to a file.
- colorized_output = sys.stdout.isatty()
-except:
- colorized_output = False
-
-# An indication of the size of your screen, or rather the size of the screen
-# to be shown, for flickrripper
-tkhorsize = 1600
-tkvertsize = 1000
-
-# ############# EXTERNAL EDITOR SETTINGS ##############
-# The command for the editor you want to use. If set to None, a simple Tkinter
-# editor will be used.
-editor = os.environ.get('EDITOR', None)
-# On Windows systems, this script tries to determine the default text editor.
-if sys.platform == 'win32':
- try:
- if sys.version_info[0] > 2:
- import winreg as _winreg
- else:
- import _winreg
- _key1 = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
-
'Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.txt\OpenWithProgids')
- _progID = _winreg.EnumValue(_key1, 1)[0]
- _key2 = _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT,
- '%s\shell\open\command' % _progID)
- _cmd = _winreg.QueryValueEx(_key2, None)[0]
- _cmd = _cmd.replace('%1', '')
- # Notepad is even worse than our Tkinter editor.
- if not _cmd.lower().endswith('notepad.exe'):
- editor = _cmd
- except WindowsError:
- # Catch any key lookup errors
- pass
-
-# Warning: DO NOT use an editor which doesn't support Unicode to edit pages!
-# You will BREAK non-ASCII symbols!
-editor_encoding = 'utf-8'
-
-# The temporary file name extension can be set in order to use syntax
-# highlighting in your text editor.
-editor_filename_extension = 'wiki'
-
-# ############# LOGFILE SETTINGS ##############
-
-# Defines for which scripts a logfile should be enabled. Logfiles will be
-# saved in the 'logs' subdirectory.
-# Example:
-# log = ['interwiki', 'weblinkchecker', 'table2wiki']
-# It is also possible to enable logging for all scripts, using this line:
-# log = ['*']
-# To disable all logging, use this:
-# log = []
-# Per default, logging of interwiki.py is enabled because its logfiles can
-# be used to generate so-called warnfiles.
-# This setting can be overridden by the -log or -nolog command-line arguments.
-log = ['interwiki']
-# filename defaults to modulename-bot.log
-logfilename = None
-# maximal size of a logfile in kilobytes. If the size reached that limit the
-# logfile will be renamed (if logfilecount is not 0) and the old file is filled
-# again. logfilesize must be an integer value
-logfilesize = 1024
-# Number of rotating logfiles are created. The older files get the higher
-# number. If logfilecount is 0, no logfile will be archived but the current
-# logfile will be overwritten if the file size reached the logfilesize above.
-# If logfilecount is -1 there are no rotating logfiles but the files where
-# renamed if the logfile is full. The newest file gets the highest number until
-# some logfiles where deleted.
-logfilecount = 5
-# set to 1 (or higher) to generate "informative" messages to terminal
-verbose_output = 0
-# set to True to fetch the pywiki version online
-log_pywiki_repo_version = False
-# if True, include a lot of debugging info in logfile
-# (overrides log setting above)
-debug_log = []
-
-# ############# INTERWIKI SETTINGS ##############
-
-# Should interwiki.py report warnings for missing links between foreign
-# languages?
-interwiki_backlink = True
-
-# Should interwiki.py display every new link it discovers?
-interwiki_shownew = True
-
-# Should interwiki.py output a graph PNG file on conflicts?
-# You need pydot for this:
-# https://pypi.python.org/pypi/pydot/1.0.2
-# https://code.google.com/p/pydot/
-interwiki_graph = False
-
-# Specifies that the robot should process that amount of subjects at a time,
-# only starting to load new pages in the original language when the total
-# falls below that number. Default is to process (at least) 100 subjects at
-# once.
-interwiki_min_subjects = 100
-
-# If interwiki graphs are enabled, which format(s) should be used?
-# Supported formats include png, jpg, ps, and svg. See:
-# http://www.graphviz.org/doc/info/output.html
-# If you want to also dump the dot files, you can use this in your
-# user-config.py:
-# interwiki_graph_formats = ['dot', 'png']
-# If you need a PNG image with an HTML image map, use this:
-# interwiki_graph_formats = ['png', 'cmap']
-# If you only need SVG images, use:
-# interwiki_graph_formats = ['svg']
-interwiki_graph_formats = ['png']
-
-# You can post the contents of your autonomous_problems.dat to the wiki,
-# e.g. to https://de.wikipedia.org/wiki/Wikipedia:Interwiki-Konflikte .
-# This allows others to assist you in resolving interwiki problems.
-# To help these people, you can upload the interwiki graphs to your
-# webspace somewhere. Set the base URL here, e.g.:
-# 'https://www.example.org/~yourname/interwiki-graphs/'
-interwiki_graph_url = None
-
-# Save file with local articles without interwikis.
-without_interwiki = False
-
-# Experimental feature:
-# Store the page contents on disk (/cache/ directory) instead of loading
-# them in RAM.
-interwiki_contents_on_disk = False
-
-# ############# SOLVE_DISAMBIGUATION SETTINGS ############
-#
-# Set disambiguation_comment[FAMILY][LANG] to a non-empty string to override
-# the default edit comment for the solve_disambiguation bot.
-# Use %s to represent the name of the disambiguation page being treated.
-# Example:
-#
-# disambiguation_comment['wikipedia']['en'] = \
-# "Robot-assisted disambiguation ([[WP:DPL|you can help!]]): %s"
-
-sort_ignore_case = False
-
-# ############# IMAGE RELATED SETTINGS ##############
-# If you set this to True, images will be uploaded to Wikimedia
-# Commons by default.
-upload_to_commons = False
-
-# ############# SETTINGS TO AVOID SERVER OVERLOAD ##############
-
-# Slow down the robot such that it never requests a second page within
-# 'minthrottle' seconds. This can be lengthened if the server is slow,
-# but never more than 'maxthrottle' seconds. However - if you are running
-# more than one bot in parallel the times are lengthened.
-# By default, the get_throttle is turned off, and 'maxlag' is used to
-# control the rate of server access. Set minthrottle to non-zero to use a
-# throttle on read access.
-minthrottle = 0
-maxthrottle = 60
-
-# Slow down the robot such that it never makes a second page edit within
-# 'put_throttle' seconds.
-put_throttle = 10
-
-# Sometimes you want to know when a delay is inserted. If a delay is larger
-# than 'noisysleep' seconds, it is logged on the screen.
-noisysleep = 3.0
-
-# Defer bot edits during periods of database server lag. For details, see
-# https://www.mediawiki.org/wiki/Maxlag_parameter
-# You can set this variable to a number of seconds, or to None (or 0) to
-# disable this behavior. Higher values are more aggressive in seeking
-# access to the wiki.
-# Non-Wikimedia wikis may or may not support this feature; for families
-# that do not use it, it is recommended to set minthrottle (above) to
-# at least 1 second.
-maxlag = 5
-
-# Maximum of pages which can be retrieved by special pages. Increase this if
-# you heavily use redirect.py with action "double", and especially if you're
-# running solve_disambiguation.py with the -primary argument.
-special_page_limit = 500
-
-# Maximum number of times to retry an API request before quitting.
-max_retries = 25
-# Minimum time to wait before resubmitting a failed API request.
-retry_wait = 5
-
-# ############# TABLE CONVERSION BOT SETTINGS ##############
-
-# will split long paragraphs for better reading the source.
-# only table2wiki.py use it by now
-splitLongParagraphs = False
-# sometimes HTML-tables are indented for better reading.
-# That can do very ugly results.
-deIndentTables = True
-# table2wiki.py works quite stable, so you might switch to True
-table2wikiAskOnlyWarnings = True
-table2wikiSkipWarnings = False
-
-# ############# WEBLINK CHECKER SETTINGS ##############
-
-# How many external links should weblinkchecker.py check at the same time?
-# If you have a fast connection, you might want to increase this number so
-# that slow servers won't slow you down.
-max_external_links = 50
-
-report_dead_links_on_talk = False
-
-# ############# DATABASE SETTINGS ##############
-# Setting to connect the database or replica of the database of the wiki.
-# db_name_format can be used to manipulate the dbName of site.
-# Example for a pywikibot running on wmflabs:
-# db_hostname = 'enwiki.labsdb'
-# db_name_format = '{0}_p'
-# db_connect_file = '~/replica.my.cnf'
-db_hostname = 'localhost'
-db_username = ''
-db_password = ''
-db_name_format = '{0}'
-db_connect_file = os.path.expanduser('~/.my.cnf')
-
-# ############# SEARCH ENGINE SETTINGS ##############
-
-# Some scripts allow using the Yahoo! Search Web Services. To use this feature,
-# you must install the pYsearch module from http://pysearch.sourceforge.net
-# and get a Yahoo AppID from https://developer.yahoo.com/
-yahoo_appid = ''
-
-# To use Windows Live Search web service you must get an AppID from
-# http://www.bing.com/dev/en-us/dev-center
-msn_appid = ''
-
-# ############# FLICKR RIPPER SETTINGS ##############
-
-# Using the Flickr api
-flickr = {
- 'api_key': u'', # Provide your key!
- 'api_secret': u'', # Api secret of your key (optional)
- 'review': False, # Do we use automatically make our uploads reviewed?
- 'reviewer': u'', # If so, under what reviewer name?
-}
-
-# ############# COPYRIGHT SETTINGS ##############
-
-# Enable/disable search engine in copyright.py script
-copyright_google = True
-copyright_yahoo = True
-copyright_msn = False
-
-# Perform a deep check, loading URLs to search if 'Wikipedia' is present.
-# This may be useful to increase the number of correct results. If you haven't
-# a fast connection, you might want to keep them disabled.
-copyright_check_in_source_google = False
-copyright_check_in_source_yahoo = False
-copyright_check_in_source_msn = False
-
-# Web pages may contain a Wikipedia text without the word 'Wikipedia' but with
-# the typical '[edit]' tag as a result of a copy & paste procedure. You want
-# no report for this kind of URLs, even if they are copyright violations.
-# However, when enabled, these URLs are logged in a file.
-copyright_check_in_source_section_names = False
-
-# Limit number of queries for page.
-copyright_max_query_for_page = 25
-
-# Skip a specified number of queries
-copyright_skip_query = 0
-
-# Number of attempts on connection error.
-copyright_connection_tries = 10
-
-# Behavior if an exceeded error occur.
-#
-# Possibilities:
-#
-# 0 = None
-# 1 = Disable search engine
-# 2 = Sleep (default)
-# 3 = Stop
-copyright_exceeded_in_queries = 2
-copyright_exceeded_in_queries_sleep_hours = 6
-
-# Append last modified date of URL to script result
-copyright_show_date = True
-
-# Append length of URL to script result
-copyright_show_length = True
-
-# By default the script tries to identify and skip text that contains a large
-# comma separated list or only numbers. But sometimes that might be the
-# only part unmodified of a slightly edited and not otherwise reported
-# copyright violation. You can disable this feature to try to increase the
-# number of results.
-copyright_economize_query = True
-
-# ############# HTTP SETTINGS ##############
-# Use a persistent http connection. An http connection has to be established
-# only once per site object, making stuff a whole lot faster. Do NOT EVER
-# use this if you share Site objects across threads without proper locking.
-#
-# DISABLED FUNCTION. Setting this variable will not have any effect.
-persistent_http = False
-
-# Default socket timeout. Set to None to disable timeouts.
-socket_timeout = 120 # set a pretty long timeout just in case...
-
-
-# ############# COSMETIC CHANGES SETTINGS ##############
-# The bot can make some additional changes to each page it edits, e.g. fix
-# whitespace or positioning of interwiki and category links.
-
-# This is an experimental feature; handle with care and consider re-checking
-# each bot edit if enabling this!
-cosmetic_changes = False
-
-# If cosmetic changes are switched on, and you also have several accounts at
-# projects where you're not familiar with the local conventions, you probably
-# only want the bot to do cosmetic changes on your "home" wiki which you
-# specified in config.mylang and config.family.
-# If you want the bot to also do cosmetic changes when editing a page on a
-# foreign wiki, set cosmetic_changes_mylang_only to False, but be careful!
-cosmetic_changes_mylang_only = True
-
-# The dictionary cosmetic_changes_enable should contain a tuple of languages
-# for each site where you wish to enable in addition to your own langlanguage
-# (if cosmetic_changes_mylang_only is set)
-# Please set your dictionary by adding such lines to your user-config.py:
-# cosmetic_changes_enable['wikipedia'] = ('de', 'en', 'fr')
-cosmetic_changes_enable = {}
-
-# The dictionary cosmetic_changes_disable should contain a tuple of languages
-# for each site where you wish to disable cosmetic changes. You may use it with
-# cosmetic_changes_mylang_only is False, but you can also disable your own
-# language. This also overrides the settings in the cosmetic_changes_enable
-# dictionary. Please set your dict by adding such lines to your user-config.py:
-# cosmetic_changes_disable['wikipedia'] = ('de', 'en', 'fr')
-cosmetic_changes_disable = {}
-
-# cosmetic_changes_deny_script is a list of scripts for which cosmetic changes
-# are disabled. You may add additional scripts by appending script names in
-# your user_config.py ("+=" operator is strictly recommended):
-# cosmetic_changes_deny_script += ['your_script_name_1', 'your_script_name_2']
-# Appending the script name also works:
-# cosmetic_changes_deny_script.append('your_script_name')
-cosmetic_changes_deny_script = ['category_redirect', 'cosmetic_changes',
- 'newitem', 'touch']
-
-# ############# REPLICATION BOT ################
-# You can add replicate_replace to your user_config.py, which has the following
-# format:
-#
-# replicate_replace = {
-# 'wikipedia:li': {'Hoofdpagina': 'Veurblaad'}
-# }
-#
-# to replace all occurrences of 'Hoofdpagina' with 'Veurblaad' when writing to
-# liwiki. Note that this does not take the origin wiki into account.
-replicate_replace = {}
-
-# ############# FURTHER SETTINGS ##############
-
-# Proxy configuration
-
-# For proxy support, install socksipy or httplib2 0.7+
-# then add these three lines to your user-config.py:
-# from httplib2 import ProxyInfo, socks
-# proxy = ProxyInfo(socks.PROXY_TYPE_HTTP, 'localhost', 8000)
-# del ProxyInfo, socks
-proxy = None
-
-# Simulate settings
-
-# Defines what additional actions the bots are NOT allowed to do (e.g. 'edit')
-# on the wiki server. Allows simulation runs of bots to be carried out without
-# changing any page on the server side. Use this setting to add more actions
-# in user-config.py for wikis with extra write actions.
-actions_to_block = []
-
-# Set simulate to True or use -simulate option to block all actions given
above.
-simulate = False
-
-# How many pages should be put to a queue in asynchronous mode.
-# If maxsize is <= 0, the queue size is infinite.
-# Increasing this value will increase memory space but could speed up
-# processing. As higher this value this effect will decrease.
-max_queue_size = 64
-
-# Define the line separator. Pages retrieved via API have "\n" whereas
-# pages fetched from screen (mostly) have "\r\n". Interwiki and category
-# separator settings in family files should use multiplied of this.
-# LS is a shortcut alias.
-line_separator = LS = u'\n'
-
-# Settings to enable mwparserfromhell
-# <https://mwparserfromhell.readthedocs.org/en/latest/>
-# Currently used in textlib.extract_templates_and_params
-# This is more accurate than our current regex, but only works
-# if the user has already installed the library.
-use_mwparserfromhell = True
-
-# Pickle protocol version to use for storing dumps.
-# This config variable is not used for loading dumps.
-# Version 2 is common to both Python 2 and 3, and should
-# be used when dumps are accessed by both versions.
-# Version 4 is only available for Python 3.4
-pickle_protocol = 2
-
-# End of configuration section
-# ============================
-
-
-def makepath(path):
- """Return a normalized absolute version of the path argument.
-
- - if the given path already exists in the filesystem
- the filesystem is not modified.
- - otherwise makepath creates directories along the given path
- using the dirname() of the path. You may append
- a '/' to the path if you want it to be a directory path.
-
- from [email protected] 2002/03/18
-
- """
- dpath = os.path.normpath(os.path.dirname(path))
- if not os.path.exists(dpath):
- os.makedirs(dpath)
- return os.path.normpath(os.path.abspath(path))
-
-
-def datafilepath(*filename):
- """Return an absolute path to a data file in a standard location.
-
- Argument(s) are zero or more directory names, optionally followed by a
- data file name. The return path is offset to config.base_dir. Any
- directories in the path that do not already exist are created.
-
- """
- return makepath(os.path.join(base_dir, *filename))
-
-
-def shortpath(path):
- """Return a file path relative to config.base_dir."""
- if path.startswith(base_dir):
- return path[len(base_dir) + len(os.path.sep):]
- return path
-# System-level and User-level changes.
-# Store current variables and their types.
-_glv = dict((_key, _val) for _key, _val in globals().items()
- if _key[0] != '_' and _key not in _imported_modules)
-_gl = list(_glv.keys())
-_tp = {}
-for _key in _gl:
- _tp[_key] = type(globals()[_key])
-
-# Create an environment for user-config.py which is
-# a shallow copy of the core config settings, so that
-# we can detect modified config items easily.
-_uc = {}
-for _key, _val in _glv.items():
- if isinstance(_val, dict):
- if isinstance(_val, collections.defaultdict):
- _uc[_key] = collections.defaultdict(dict)
- else:
- _uc[_key] = {}
- if len(_val) > 0:
- _uc[_key].update(_val)
- else:
- _uc[_key] = _val
-
-# Get the user files
-_thislevel = 0
-if os.environ.get('PYWIKIBOT2_NO_USER_CONFIG', '0') == '1':
- print("WARNING: Skipping loading of user-config.py.")
- _fns = []
-else:
- _fns = [os.path.join(_base_dir, "user-config.py")]
-for _filename in _fns:
- _thislevel += 1
- if os.path.exists(_filename):
- _filestatus = os.stat(_filename)
- _filemode = _filestatus[0]
- _fileuid = _filestatus[4]
- if sys.platform == 'win32' or _fileuid in [os.getuid(), 0]:
- if sys.platform == 'win32' or _filemode & 0o02 == 0:
- exec(compile(open(_filename).read(), _filename, 'exec'), _uc)
- else:
- print("WARNING: Skipped '%(fn)s': writeable by others."
- % {'fn': _filename})
- else:
- print("WARNING: Skipped '%(fn)s': owned by someone else."
- % {'fn': _filename})
-
-# Test for obsoleted and/or unknown variables.
-for _key, _val in list(_uc.items()):
- if _key.startswith('_'):
- pass
- elif _key in _imported_modules:
- pass
- elif _key in _gl:
- nt = type(_val)
- ot = _tp[_key]
- ov = _glv[_key]
-
- if nt == ot or _val is None or ov is None: # nopep8
- pass
- elif nt is int and (ot is float or ot is bool):
- pass
- elif ot is int and (nt is float or nt is bool):
- pass
- else:
- print("WARNING: Type of '%(_key)s' changed" % locals())
- print(" %(was)s: %(old)s" % {'was': "Was", 'old': ot})
- print(" %(now)s: %(new)s" % {'now': "Now", 'new': nt})
- del nt, ot, ov
- else:
- print("WARNING: "
- "Configuration variable %(_key)r is defined but unknown.\n"
- "Misspelled?" % locals())
-
-# Copy the user config settings into globals
-_modified = [_key for _key in _gl
- if _uc[_key] != globals()[_key] or
- _key in ('usernames', 'sysopnames', 'disambiguation_comment')]
-
-for _key in _modified:
- globals()[_key] = _uc[_key]
-
- if _key in _deprecated_variables:
- print("WARNING: '%s' is no longer a supported configuration variable."
- "\nPlease inform the maintainers if you depend on it." % _key)
-
-# Fix up default console_encoding
-if console_encoding is None:
- if sys.platform == 'win32':
- console_encoding = 'cp850'
- else:
- console_encoding = 'iso-8859-1'
-
-# Fix up transliteration_target
-if transliteration_target == 'not set':
- if sys.platform == 'win32':
- transliteration_target = console_encoding
- print("WARNING: Running on Windows and transliteration_target is not "
- "set.")
- print("Please see "
-
"https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Pywikibot/Windows")
- else:
- transliteration_target = None
-elif transliteration_target in ('None', 'none'):
- transliteration_target = None
-
-if sys.platform == 'win32' and editor:
- # single character string literals from
- # https://docs.python.org/2/reference/lexical_analysis.html#string-literals
- # encode('unicode-escape') also changes Unicode characters
- if set(editor) & set('\a\b\f\n\r\t\v'):
- print('WARNING: The editor path contains probably invalid escaped '
- 'characters. Make sure to use a raw-string (r"..." or r\'...\'),
'
- 'forward slashs as a path delimiter or to escape the normal '
- 'path delimiter.')
-
-
-# Fix up default site
-if family == 'wikipedia' and mylang == 'language':
- print("WARNING: family and mylang are not set.\n"
- "Defaulting to family='test' and mylang='test'.")
- family = mylang = 'test'
-
-#
-# When called as main program, list all configuration variables
-#
-if __name__ == "__main__":
- import types
- _all = 1
- for _arg in sys.argv[1:]:
- if _arg == "modified":
- _all = 0
- else:
- print("Unknown arg %(_arg)s ignored" % locals())
- _k = list(globals().keys())
- _k.sort()
- for _name in _k:
- if _name[0] != '_':
- if not type(globals()[_name]) in [types.FunctionType,
- types.ModuleType]:
- if _all or _name in _modified:
- _value = globals()[_name]
- if _name in _private_values and _value:
- if isinstance(_value, dict):
- _value = '{ ...xxxxxxxx... }'
- elif hasattr(_value, '__dict__'):
- _value = '%s( ...xxxxxxxx... )' % \
- _value.__class__.__name__
- else:
- _value = repr('xxxxxxxx')
- else:
- _value = repr(_value)
- print("%s=%s" % (_name, _value))
-
-# cleanup all locally-defined variables
-for __var in list(globals().keys()):
- if __var.startswith("_") and not __var.startswith("__"):
- del sys.modules[__name__].__dict__[__var]
-
-del __var
+_ConfigDeprecationWrapper()
diff --git a/pywikibot/configuration.py b/pywikibot/configuration.py
new file mode 100644
index 0000000..d7e76df
--- /dev/null
+++ b/pywikibot/configuration.py
@@ -0,0 +1,954 @@
+# -*- coding: utf-8 -*-
+"""
+Module to define and load pywikibot configuration.
+
+Provides two family class methods which can be used in
+the user-config:
+ - register_family_file
+ - register_families_folder
+
+Sets module global base_dir and provides utility methods to
+build paths relative to base_dir:
+ - makepath
+ - datafilepath
+ - shortpath
+"""
+#
+# (C) Rob W.W. Hooft, 2003
+# (C) Pywikibot team, 2003-2015
+#
+# Distributed under the terms of the MIT license.
+#
+__version__ = '$Id$'
+#
+
+import codecs
+import collections
+import os
+import sys
+
+
+class _Configuration(object):
+
+ # IMPORTANT:
+ # Do not change any of the variables in this file. Instead, make
+ # a file user-config.py, and overwrite values in there.
+
+ # Note: all variables defined in this module are made available to bots as
+ # configuration settings, *except* variable names beginning with an
+ # underscore (example: _variable). Be sure to use an underscore on any
+ # variables that are intended only for internal use and not to be exported
+ # to other modules.
+
+ _private_values = ['authenticate', 'proxy', 'db_password']
+ _deprecated_variables = ['use_SSL_onlogin', 'use_SSL_always',
+ 'available_ssl_project_comment']
+
+ # ############# ACCOUNT SETTINGS ##############
+
+ # The family of sites we are working on. pywikibot will import
+ # families/xxx_family.py so if you want to change this variable,
+ # you need to write such a file if one does not exist.
+ family = 'wikipedia'
+ # The language code of the site we're working on.
+ mylang = 'language'
+ # If family and mylang are not modified from the above, it is changed
+ # to test:test, which is test.wikipedia.org, at the end of this module.
+
+ # The dictionary usernames should contain a username for each site where
you
+ # have a bot account. Please set your usernames by adding such lines to
your
+ # user-config.py:
+ #
+ # usernames['wikipedia']['de'] = 'myGermanUsername'
+ # usernames['wiktionary']['en'] = 'myEnglishUsername'
+ #
+ # If you have a unique username for all languages of a family,
+ # you can use '*'
+ # usernames['wikibooks']['*'] = 'mySingleUsername'
+ #
+ # If you have a sysop account on some wikis, this will be used to delete
+ # pages or to edit locked pages if you add such lines to your
+ # user-config.py:
+ #
+ # sysopnames['wikipedia']['de'] = 'myGermanUsername'
+ # sysopnames['wiktionary']['en'] = 'myEnglishUsername'
+ #
+ # If you have a unique syop account for all languages of a family,
+ # you can use '*'
+ # sysopnames['myownwiki']['*'] = 'mySingleUsername'
+ usernames = collections.defaultdict(dict)
+ sysopnames = collections.defaultdict(dict)
+ disambiguation_comment = collections.defaultdict(dict)
+
+ # User agent format.
+ # For the meaning and more help in customization see:
+ # https://www.mediawiki.org/wiki/Manual:Pywikibot/User-agent
+ user_agent_format = ('{script_product} ({script_comments}) {pwb} '
+ '({revision}) {httplib2} {python}')
+
+ # The default interface for communicating with the site
+ # currently the only defined interface is 'APISite', so don't change this!
+ site_interface = 'APISite'
+ # number of days to cache namespaces, api configuration, etc.
+ API_config_expiry = 30
+
+ # The maximum number of bytes which uses a GET request, if not positive
+ # it'll always use POST requests
+ maximum_GET_length = 255
+ # Some networks modify GET requests when they are not encrypted, to avoid
+ # bug reports related to that disable those. If we are confident that bug
+ # related to this are really because of the network this could be changed.
+ enable_GET_without_SSL = False
+
+ # Solve captchas in the webbrowser. Setting this to False will result in
the
+ # exception CaptchaError being thrown if a captcha is encountered.
+ solve_captcha = True
+
+ # Some sites will require password authentication to access the HTML pages
+ # at the site. If you have any such site, add lines to your user-config.py
+ # of the following form:
+ #
+ # authenticate['en.wikipedia.org'] = ('John','XXXXX')
+ #
+ # where John is your login name, and XXXXX your password.
+ # Note:
+ # 1. This is only for sites that use authentication in the form that gives
+ # you a popup for name and password when you try to access any data, NOT
+ # for, for example, wiki usernames
+ # 2. You must use the hostname of the site, not its family/language pair
+ authenticate = {}
+
+ #
+ # Secure connection overrides
+ #
+ # These settings are deprecated. They existed to support the Wikimedia
+ # family which only served HTTPS on
https://secure.wikimedia.org/<site>/<uri>
+ # Use Family.protocol()
+ use_SSL_onlogin = False # if available, use SSL when logging in
+ use_SSL_always = False # if available, use SSL for all API queries
+ # Available secure projects should be listed here.
+ available_ssl_project = []
+
+ # By default you are asked for a password on the terminal.
+ # A password file may be used. e.g. password_file = ".passwd"
+ # The password file should consist of lines containing
+ # Python tuples of any of the following formats:
+ # (code, family, username, password)
+ # (family, username, password)
+ # (username, password)
+ password_file = None
+
+ # edit summary to use if not supplied by bot script
+ # WARNING: this should NEVER be used in practice, ALWAYS supply a more
+ # relevant summary for bot edits
+ default_edit_summary = u'Pywikibot v.2'
+
+ # Use 'register_family_file' and 'register_families_folder' to fill
+ # this dictionary
+ family_files = {}
+
+ # Set to True to override the {{bots}} exclusion protocol (at your own
risk!)
+ ignore_bot_templates = False
+
+ # ############# USER INTERFACE SETTINGS ##############
+
+ # The encoding that's used in the user's console, i.e. how strings are
encoded
+ # when they are read by raw_input(). On Windows systems' DOS box, this
should
+ # be 'cp850' ('cp437' for older versions). Linux users might try
'iso-8859-1'
+ # or 'utf-8'.
+ # This default code should work fine, so you don't have to think about it.
+ # TODO: consider getting rid of this config variable.
+ try:
+ console_encoding = sys.stdout.encoding
+ except:
+ # When using pywikibot inside a daemonized twisted application,
+ # we get "StdioOnnaStick instance has no attribute 'encoding'"
+ console_encoding = None
+
+ # The encoding the user would like to see text transliterated to. This can
be
+ # set to a charset (e.g. 'ascii', 'iso-8859-1' or 'cp850'), and we will
output
+ # only characters that exist in that charset. However, the characters will
be
+ # output using console_encoding.
+ # If this is not defined on Windows, we emit a Warning explaining the user
+ # to either switch to a Unicode-able font and use
+ # transliteration_target = None
+ # or to keep using raster fonts and set
+ # transliteration_target = console_encoding
+ # After emitting the warning, this last option will be set.
+ transliteration_target = 'not set'
+
+ # The encoding in which textfiles are stored, which contain lists of page
+ # titles. The most used is: 'utf-8'. 'utf-8-sig' requires a BOM.
+ # For a complete list please see:
+ # https://docs.python.org/2/library/codecs.html#standard-encodings
+ textfile_encoding = 'utf-8'
+
+ # tkinter isn't yet ready
+ userinterface = 'terminal'
+
+ # this can be used to pass variables to the UI init function
+ # useful for e.g.
+ # userinterface_init_kwargs = {'default_stream': 'stdout'}
+ userinterface_init_kwargs = {}
+
+ # i18n setting for user interface language
+ # default is config.mylang or 'en'
+ userinterface_lang = None
+
+ # Should we transliterate characters that do not exist in the console
+ # character set?
+ # True: whenever possible
+ # False: never - always replace them by question marks
+ # Currently only works if interface 'terminal' is set.
+ transliterate = True
+
+ # Should the system bell ring if the bot expects user input?
+ ring_bell = False
+
+ # Colorization can be used to markup important text parts of the output.
+ # On Linux/Unix terminals, ANSI escape codes are used for this. On Windows,
+ # it is done by a DLL call via ctypes.
+ # Set this to False if you're using Linux and your tty doesn't support
+ # ANSI colors.
+ try:
+ # Don't print colorized when the output is, for example, piped to a
file.
+ colorized_output = sys.stdout.isatty()
+ except:
+ colorized_output = False
+
+ # An indication of the size of your screen, or rather the size of the
screen
+ # to be shown, for flickrripper
+ tkhorsize = 1600
+ tkvertsize = 1000
+
+ # ############# EXTERNAL EDITOR SETTINGS ##############
+ # The command for the editor you want to use. If set to None, a simple
Tkinter
+ # editor will be used.
+ editor = os.environ.get('EDITOR', None)
+ # On Windows systems, this script tries to determine the default text
editor.
+ if sys.platform == 'win32':
+ try:
+ if sys.version_info[0] > 2:
+ import winreg as _winreg
+ else:
+ import _winreg
+ _key1 = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
+
'Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.txt\OpenWithProgids')
+ _progID = _winreg.EnumValue(_key1, 1)[0]
+ _key2 = _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT,
+ '%s\shell\open\command' % _progID)
+ _cmd = _winreg.QueryValueEx(_key2, None)[0]
+ _cmd = _cmd.replace('%1', '')
+ # Notepad is even worse than our Tkinter editor.
+ if not _cmd.lower().endswith('notepad.exe'):
+ editor = _cmd
+ except WindowsError:
+ # Catch any key lookup errors
+ pass
+
+ # Warning: DO NOT use an editor which doesn't support Unicode to edit
pages!
+ # You will BREAK non-ASCII symbols!
+ editor_encoding = 'utf-8'
+
+ # The temporary file name extension can be set in order to use syntax
+ # highlighting in your text editor.
+ editor_filename_extension = 'wiki'
+
+ # ############# LOGFILE SETTINGS ##############
+
+ # Defines for which scripts a logfile should be enabled. Logfiles will be
+ # saved in the 'logs' subdirectory.
+ # Example:
+ # log = ['interwiki', 'weblinkchecker', 'table2wiki']
+ # It is also possible to enable logging for all scripts, using this line:
+ # log = ['*']
+ # To disable all logging, use this:
+ # log = []
+ # Per default, logging of interwiki.py is enabled because its logfiles can
+ # be used to generate so-called warnfiles.
+ # This setting can be overridden by the -log or -nolog command-line
arguments.
+ log = ['interwiki']
+ # filename defaults to modulename-bot.log
+ logfilename = None
+ # maximal size of a logfile in kilobytes. If the size reached that limit
the
+ # logfile will be renamed (if logfilecount is not 0) and the old file is
filled
+ # again. logfilesize must be an integer value
+ logfilesize = 1024
+ # Number of rotating logfiles are created. The older files get the higher
+ # number. If logfilecount is 0, no logfile will be archived but the current
+ # logfile will be overwritten if the file size reached the logfilesize
above.
+ # If logfilecount is -1 there are no rotating logfiles but the files where
+ # renamed if the logfile is full. The newest file gets the highest number
until
+ # some logfiles where deleted.
+ logfilecount = 5
+ # set to 1 (or higher) to generate "informative" messages to terminal
+ verbose_output = 0
+ # set to True to fetch the pywiki version online
+ log_pywiki_repo_version = False
+ # if True, include a lot of debugging info in logfile
+ # (overrides log setting above)
+ debug_log = []
+
+ # ############# INTERWIKI SETTINGS ##############
+
+ # Should interwiki.py report warnings for missing links between foreign
+ # languages?
+ interwiki_backlink = True
+
+ # Should interwiki.py display every new link it discovers?
+ interwiki_shownew = True
+
+ # Should interwiki.py output a graph PNG file on conflicts?
+ # You need pydot for this:
+ # https://pypi.python.org/pypi/pydot/1.0.2
+ # https://code.google.com/p/pydot/
+ interwiki_graph = False
+
+ # Specifies that the robot should process that amount of subjects at a
time,
+ # only starting to load new pages in the original language when the total
+ # falls below that number. Default is to process (at least) 100 subjects at
+ # once.
+ interwiki_min_subjects = 100
+
+ # If interwiki graphs are enabled, which format(s) should be used?
+ # Supported formats include png, jpg, ps, and svg. See:
+ # http://www.graphviz.org/doc/info/output.html
+ # If you want to also dump the dot files, you can use this in your
+ # user-config.py:
+ # interwiki_graph_formats = ['dot', 'png']
+ # If you need a PNG image with an HTML image map, use this:
+ # interwiki_graph_formats = ['png', 'cmap']
+ # If you only need SVG images, use:
+ # interwiki_graph_formats = ['svg']
+ interwiki_graph_formats = ['png']
+
+ # You can post the contents of your autonomous_problems.dat to the wiki,
+ # e.g. to https://de.wikipedia.org/wiki/Wikipedia:Interwiki-Konflikte .
+ # This allows others to assist you in resolving interwiki problems.
+ # To help these people, you can upload the interwiki graphs to your
+ # webspace somewhere. Set the base URL here, e.g.:
+ # 'https://www.example.org/~yourname/interwiki-graphs/'
+ interwiki_graph_url = None
+
+ # Save file with local articles without interwikis.
+ without_interwiki = False
+
+ # Experimental feature:
+ # Store the page contents on disk (/cache/ directory) instead of loading
+ # them in RAM.
+ interwiki_contents_on_disk = False
+
+ # ############# SOLVE_DISAMBIGUATION SETTINGS ############
+ #
+ # Set disambiguation_comment[FAMILY][LANG] to a non-empty string to
override
+ # the default edit comment for the solve_disambiguation bot.
+ # Use %s to represent the name of the disambiguation page being treated.
+ # Example:
+ #
+ # disambiguation_comment['wikipedia']['en'] = \
+ # "Robot-assisted disambiguation ([[WP:DPL|you can help!]]): %s"
+
+ sort_ignore_case = False
+
+ # ############# IMAGE RELATED SETTINGS ##############
+ # If you set this to True, images will be uploaded to Wikimedia
+ # Commons by default.
+ upload_to_commons = False
+
+ # ############# SETTINGS TO AVOID SERVER OVERLOAD ##############
+
+ # Slow down the robot such that it never requests a second page within
+ # 'minthrottle' seconds. This can be lengthened if the server is slow,
+ # but never more than 'maxthrottle' seconds. However - if you are running
+ # more than one bot in parallel the times are lengthened.
+ # By default, the get_throttle is turned off, and 'maxlag' is used to
+ # control the rate of server access. Set minthrottle to non-zero to use a
+ # throttle on read access.
+ minthrottle = 0
+ maxthrottle = 60
+
+ # Slow down the robot such that it never makes a second page edit within
+ # 'put_throttle' seconds.
+ put_throttle = 10
+
+ # Sometimes you want to know when a delay is inserted. If a delay is larger
+ # than 'noisysleep' seconds, it is logged on the screen.
+ noisysleep = 3.0
+
+ # Defer bot edits during periods of database server lag. For details, see
+ # https://www.mediawiki.org/wiki/Maxlag_parameter
+ # You can set this variable to a number of seconds, or to None (or 0) to
+ # disable this behavior. Higher values are more aggressive in seeking
+ # access to the wiki.
+ # Non-Wikimedia wikis may or may not support this feature; for families
+ # that do not use it, it is recommended to set minthrottle (above) to
+ # at least 1 second.
+ maxlag = 5
+
+ # Maximum of pages which can be retrieved by special pages. Increase this
if
+ # you heavily use redirect.py with action "double", and especially if
you're
+ # running solve_disambiguation.py with the -primary argument.
+ special_page_limit = 500
+
+ # Maximum number of times to retry an API request before quitting.
+ max_retries = 25
+ # Minimum time to wait before resubmitting a failed API request.
+ retry_wait = 5
+
+ # ############# TABLE CONVERSION BOT SETTINGS ##############
+
+ # will split long paragraphs for better reading the source.
+ # only table2wiki.py use it by now
+ splitLongParagraphs = False
+ # sometimes HTML-tables are indented for better reading.
+ # That can do very ugly results.
+ deIndentTables = True
+ # table2wiki.py works quite stable, so you might switch to True
+ table2wikiAskOnlyWarnings = True
+ table2wikiSkipWarnings = False
+
+ # ############# WEBLINK CHECKER SETTINGS ##############
+
+ # How many external links should weblinkchecker.py check at the same time?
+ # If you have a fast connection, you might want to increase this number so
+ # that slow servers won't slow you down.
+ max_external_links = 50
+
+ report_dead_links_on_talk = False
+
+ # ############# DATABASE SETTINGS ##############
+ # Setting to connect the database or replica of the database of the wiki.
+ # db_name_format can be used to manipulate the dbName of site.
+ # Example for a pywikibot running on wmflabs:
+ # db_hostname = 'enwiki.labsdb'
+ # db_name_format = '{0}_p'
+ # db_connect_file = '~/replica.my.cnf'
+ db_hostname = 'localhost'
+ db_username = ''
+ db_password = ''
+ db_name_format = '{0}'
+ db_connect_file = os.path.expanduser('~/.my.cnf')
+
+ # ############# SEARCH ENGINE SETTINGS ##############
+
+ # Some scripts allow using the Yahoo! Search Web Services. To use this
feature,
+ # you must install the pYsearch module from http://pysearch.sourceforge.net
+ # and get a Yahoo AppID from https://developer.yahoo.com/
+ yahoo_appid = ''
+
+ # To use Windows Live Search web service you must get an AppID from
+ # http://www.bing.com/dev/en-us/dev-center
+ msn_appid = ''
+
+ # ############# FLICKR RIPPER SETTINGS ##############
+
+ # Using the Flickr api
+ flickr = {
+ 'api_key': u'', # Provide your key!
+ 'api_secret': u'', # Api secret of your key (optional)
+ 'review': False, # Do we use automatically make our uploads reviewed?
+ 'reviewer': u'', # If so, under what reviewer name?
+ }
+
+ # ############# COPYRIGHT SETTINGS ##############
+
+ # Enable/disable search engine in copyright.py script
+ copyright_google = True
+ copyright_yahoo = True
+ copyright_msn = False
+
+ # Perform a deep check, loading URLs to search if 'Wikipedia' is present.
+ # This may be useful to increase the number of correct results. If you
haven't
+ # a fast connection, you might want to keep them disabled.
+ copyright_check_in_source_google = False
+ copyright_check_in_source_yahoo = False
+ copyright_check_in_source_msn = False
+
+ # Web pages may contain a Wikipedia text without the word 'Wikipedia' but
with
+ # the typical '[edit]' tag as a result of a copy & paste procedure. You
want
+ # no report for this kind of URLs, even if they are copyright violations.
+ # However, when enabled, these URLs are logged in a file.
+ copyright_check_in_source_section_names = False
+
+ # Limit number of queries for page.
+ copyright_max_query_for_page = 25
+
+ # Skip a specified number of queries
+ copyright_skip_query = 0
+
+ # Number of attempts on connection error.
+ copyright_connection_tries = 10
+
+ # Behavior if an exceeded error occur.
+ #
+ # Possibilities:
+ #
+ # 0 = None
+ # 1 = Disable search engine
+ # 2 = Sleep (default)
+ # 3 = Stop
+ copyright_exceeded_in_queries = 2
+ copyright_exceeded_in_queries_sleep_hours = 6
+
+ # Append last modified date of URL to script result
+ copyright_show_date = True
+
+ # Append length of URL to script result
+ copyright_show_length = True
+
+ # By default the script tries to identify and skip text that contains a
large
+ # comma separated list or only numbers. But sometimes that might be the
+ # only part unmodified of a slightly edited and not otherwise reported
+ # copyright violation. You can disable this feature to try to increase the
+ # number of results.
+ copyright_economize_query = True
+
+ # ############# HTTP SETTINGS ##############
+ # Use a persistent http connection. An http connection has to be
established
+ # only once per site object, making stuff a whole lot faster. Do NOT EVER
+ # use this if you share Site objects across threads without proper locking.
+ #
+ # DISABLED FUNCTION. Setting this variable will not have any effect.
+ persistent_http = False
+
+ # Default socket timeout. Set to None to disable timeouts.
+ socket_timeout = 120 # set a pretty long timeout just in case...
+
+ # ############# COSMETIC CHANGES SETTINGS ##############
+ # The bot can make some additional changes to each page it edits, e.g. fix
+ # whitespace or positioning of interwiki and category links.
+
+ # This is an experimental feature; handle with care and consider
re-checking
+ # each bot edit if enabling this!
+ cosmetic_changes = False
+
+ # If cosmetic changes are switched on, and you also have several accounts
at
+ # projects where you're not familiar with the local conventions, you
probably
+ # only want the bot to do cosmetic changes on your "home" wiki which you
+ # specified in config.mylang and config.family.
+ # If you want the bot to also do cosmetic changes when editing a page on a
+ # foreign wiki, set cosmetic_changes_mylang_only to False, but be careful!
+ cosmetic_changes_mylang_only = True
+
+ # The dictionary cosmetic_changes_enable should contain a tuple of
languages
+ # for each site where you wish to enable in addition to your own
langlanguage
+ # (if cosmetic_changes_mylang_only is set)
+ # Please set your dictionary by adding such lines to your user-config.py:
+ # cosmetic_changes_enable['wikipedia'] = ('de', 'en', 'fr')
+ cosmetic_changes_enable = {}
+
+ # The dictionary cosmetic_changes_disable should contain a tuple of
languages
+ # for each site where you wish to disable cosmetic changes. You may use it
with
+ # cosmetic_changes_mylang_only is False, but you can also disable your own
+ # language. This also overrides the settings in the cosmetic_changes_enable
+ # dictionary. Please set your dict by adding such lines to your
user-config.py:
+ # cosmetic_changes_disable['wikipedia'] = ('de', 'en', 'fr')
+ cosmetic_changes_disable = {}
+
+ # cosmetic_changes_deny_script is a list of scripts for which cosmetic
changes
+ # are disabled. You may add additional scripts by appending script names in
+ # your user_config.py ("+=" operator is strictly recommended):
+ # cosmetic_changes_deny_script += ['your_script_name_1',
'your_script_name_2']
+ # Appending the script name also works:
+ # cosmetic_changes_deny_script.append('your_script_name')
+ cosmetic_changes_deny_script = ['category_redirect', 'cosmetic_changes',
+ 'newitem', 'touch']
+
+ # ############# REPLICATION BOT ################
+ # You can add replicate_replace to your user_config.py, which has the
following
+ # format:
+ #
+ # replicate_replace = {
+ # 'wikipedia:li': {'Hoofdpagina': 'Veurblaad'}
+ # }
+ #
+ # to replace all occurrences of 'Hoofdpagina' with 'Veurblaad' when
writing to
+ # liwiki. Note that this does not take the origin wiki into account.
+ replicate_replace = {}
+
+ # ############# FURTHER SETTINGS ##############
+
+ # Proxy configuration
+
+ # For proxy support, install socksipy or httplib2 0.7+
+ # then add these three lines to your user-config.py:
+ # from httplib2 import ProxyInfo, socks
+ # proxy = ProxyInfo(socks.PROXY_TYPE_HTTP, 'localhost', 8000)
+ # del ProxyInfo, socks
+ proxy = None
+
+ # Simulate settings
+
+ # Defines what additional actions the bots are NOT allowed to do (e.g.
'edit')
+ # on the wiki server. Allows simulation runs of bots to be carried out
without
+ # changing any page on the server side. Use this setting to add more
actions
+ # in user-config.py for wikis with extra write actions.
+ actions_to_block = []
+
+ # Set simulate to True or use -simulate option to block all actions given
above.
+ simulate = False
+
+ # How many pages should be put to a queue in asynchronous mode.
+ # If maxsize is <= 0, the queue size is infinite.
+ # Increasing this value will increase memory space but could speed up
+ # processing. As higher this value this effect will decrease.
+ max_queue_size = 64
+
+ # Define the line separator. Pages retrieved via API have "\n" whereas
+ # pages fetched from screen (mostly) have "\r\n". Interwiki and category
+ # separator settings in family files should use multiplied of this.
+ # LS is a shortcut alias.
+ line_separator = LS = u'\n'
+
+ # Settings to enable mwparserfromhell
+ # <https://mwparserfromhell.readthedocs.org/en/latest/>
+ # Currently used in textlib.extract_templates_and_params
+ # This is more accurate than our current regex, but only works
+ # if the user has already installed the library.
+ use_mwparserfromhell = True
+
+ # Pickle protocol version to use for storing dumps.
+ # This config variable is not used for loading dumps.
+ # Version 2 is common to both Python 2 and 3, and should
+ # be used when dumps are accessed by both versions.
+ # Version 4 is only available for Python 3.4
+ pickle_protocol = 2
+
+ # End of configuration section
+ # ============================
+
+ def __init__(self, base_dir):
+ self.base_dir = base_dir
+ # Get the names of all known families, and initialize with empty
+ # dictionaries. ‘families/’ is a subdirectory of the directory in
+ # which this Python file is found.
+ self.register_families_folder(os.path.join(os.path.dirname(__file__),
'families'))
+ self.register_family_file('wikiapiary', 'https://wikiapiary.com')
+
+ def makepath(self, path):
+ """Return a normalized absolute version of the path argument.
+
+ - if the given path already exists in the filesystem
+ the filesystem is not modified.
+ - otherwise makepath creates directories along the given path
+ using the dirname() of the path. You may append
+ a '/' to the path if you want it to be a directory path.
+
+ from [email protected] 2002/03/18
+
+ """
+ dpath = os.path.normpath(os.path.dirname(path))
+ if not os.path.exists(dpath):
+ os.makedirs(dpath)
+ return os.path.normpath(os.path.abspath(path))
+
+ def datafilepath(self, *filename):
+ """Return an absolute path to a data file in a standard location.
+
+ Argument(s) are zero or more directory names, optionally followed by a
+ data file name. The return path is offset to config.base_dir. Any
+ directories in the path that do not already exist are created.
+
+ """
+ return self.makepath(os.path.join(self.base_dir, *filename))
+
+ def shortpath(self, path):
+ """Return a file path relative to config.base_dir."""
+ if path.startswith(self.base_dir):
+ return path[len(self.base_dir) + len(os.path.sep):]
+ return path
+
+ def register_family_file(self, family_name, file_path):
+ """Register a single family class file."""
+ # TODO: Are those necessary? They are defaultdict
+ self.usernames[family_name] = {}
+ self.sysopnames[family_name] = {}
+ self.disambiguation_comment[family_name] = {}
+ self.family_files[family_name] = file_path
+
+ def register_families_folder(self, folder_path):
+ """Register all family class files contained in a directory."""
+ for file_name in os.listdir(folder_path):
+ if file_name.endswith("_family.py"):
+ family_name = file_name[:-len("_family.py")]
+ self.register_family_file(family_name,
+ os.path.join(folder_path, file_name))
+
+
+def _parse_arg(args):
+ args2 = args[:]
+ args = []
+ config_dir = None
+ for arg in args2:
+ if config_dir is None and arg.startswith('-dir:'):
+ config_dir = os.path.expanduser(arg[len('-dir:'):])
+ else:
+ args.append(arg)
+ return args, config_dir
+
+
+def get_base_dir(args_dir, test_directory=None):
+ r"""Return the directory in which user-specific information is stored.
+
+ This is determined in the following order:
+ 1. If the script was called with a -dir: argument, use the directory
+ provided in this argument.
+ 2. If the user has a PYWIKIBOT2_DIR environment variable, use the value
+ of it.
+ 3. If user-config is present in current directory, use the current
+ directory.
+ 4. If user-config is present in pwb.py directory, use that directory
+ 5. Use (and if necessary create) a 'pywikibot' folder under
+ 'Application Data' or 'AppData\Roaming' (Windows) or
+ '.pywikibot' directory (Unix and similar) under the user's home
+ directory.
+
+ Set PYWIKIBOT2_NO_USER_CONFIG=1 to disable loading user-config.py
+
+ @param test_directory: Assume that a user config file exists in this
+ directory. Used to test whether placing a user config file in this
+ directory will cause it to be selected as the base directory.
+ @type test_directory: str or None
+ @rtype: unicode
+ """
+ def exists(directory):
+ directory = os.path.abspath(directory)
+ if directory == test_directory:
+ return True
+ else:
+ return os.path.exists(os.path.join(directory, 'user-config.py'))
+
+ if test_directory is not None:
+ test_directory = os.path.abspath(test_directory)
+
+ DIRNAME_WIN = u"Pywikibot"
+ DIRNAME_WIN_FBCK = u"pywikibot"
+ DIRNAME_UNIX = u".pywikibot"
+
+ if args_dir is not None:
+ base_dir = args_dir
+ else:
+ if ('PYWIKIBOT2_DIR' in os.environ and
+ exists(os.path.abspath(os.environ['PYWIKIBOT2_DIR']))):
+ base_dir = os.path.abspath(os.environ['PYWIKIBOT2_DIR'])
+ elif exists('.'):
+ base_dir = os.path.abspath('.')
+ elif ('PYWIKIBOT2_DIR_PWB' in os.environ and
+ exists(os.path.abspath(os.environ['PYWIKIBOT2_DIR_PWB']))):
+ base_dir = os.path.abspath(os.environ['PYWIKIBOT2_DIR_PWB'])
+ else:
+ base_dir_cand = []
+ home = os.path.expanduser("~")
+ if sys.platform == 'win32':
+ import platform
+ win_version = int(platform.version()[0])
+ if win_version == 5:
+ sub_dir = ["Application Data"]
+ elif win_version == 6:
+ sub_dir = ["AppData", "Roaming"]
+ else:
+ raise WindowsError(u'Windows version %s not supported yet.'
+ % win_version)
+ base_dir_cand.extend([[home] + sub_dir + [DIRNAME_WIN],
+ [home] + sub_dir + [DIRNAME_WIN_FBCK]])
+ else:
+ base_dir_cand.append([home, DIRNAME_UNIX])
+
+ base_dir = ""
+ for dir in base_dir_cand:
+ dir = os.path.join(*dir)
+ if not os.path.isdir(dir):
+ os.makedirs(dir, mode=0o700)
+ if exists(dir):
+ base_dir = dir
+ break
+
+ if not os.path.isabs(base_dir):
+ base_dir = os.path.normpath(os.path.join(os.getcwd(), base_dir))
+ # make sure this path is valid and that it contains user-config file
+ if not os.path.isdir(base_dir):
+ raise RuntimeError("Directory '%s' does not exist." % base_dir)
+ # check if user-config.py is in base_dir
+ if not exists(base_dir):
+ exc_text = "No user-config.py found in directory '%s'.\n" % base_dir
+ if os.environ.get('PYWIKIBOT2_NO_USER_CONFIG', '0') == '1':
+ print(exc_text)
+ else:
+ exc_text += " Please check that user-config.py is stored in the
correct location.\n"
+ exc_text += " Directory where user-config.py is searched is
determined as follows:\n\n"
+ exc_text += " " + get_base_dir.__doc__
+ raise RuntimeError(exc_text)
+
+ return base_dir
+
+
+class _ConfigurationWrapper(object):
+
+ """
+ A wrapper for the configuration class.
+
+ This class shouldn't be instantiated outside of this module. It delegates
+ all attribute access to the configuration class unless it's about handling
+ that configuration class. It is needed for lazy loading the configuration
+ values, to give using scripts a chance to set the path themselves.
+
+ It contains a normal configuration (which is used) and a default
+ configuration which is only used for comparison of the values after loading
+ the user-config.py.
+ """
+
+ def __init__(self):
+ super(_ConfigurationWrapper, self).__setattr__('_configuration', None)
+
+ def __setattr__(self, name, value):
+ if not self._configuration:
+ self._load(None)
+ setattr(self._configuration, name, value)
+
+ def __getattr__(self, name):
+ if not self._configuration:
+ self._load(None)
+ return getattr(self._configuration, name)
+
+ def _load(self, path):
+ path = get_base_dir(path)
+ super(_ConfigurationWrapper, self).__setattr__('_configuration',
_Configuration(path))
+
+ if os.environ.get('PYWIKIBOT2_NO_USER_CONFIG', '0') == '1':
+ # TODO: Use pywikibot.warning
+ print("WARNING: Skipping loading of user-config.py.")
+ return
+
+ user_config = dict(item for item in _Configuration.__dict__.items()
+ if item[0][0] != '_')
+
+ # Get the user files
+ file_name = os.path.join(path, 'user-config.py')
+ if sys.platform != 'win32':
+ filestatus = os.stat(file_name)
+ filemode = filestatus[0]
+ fileuid = filestatus[4]
+ else:
+ # On Windows those are not meaningful
+ filemode = 0
+ fileuid = 0
+ if fileuid in [os.getuid(), 0]:
+ if filemode & 0o02 == 0:
+ with codecs.open(file_name, encoding='utf8') as f:
+ exec(compile(f.read(), file_name, 'exec'), user_config)
+ else:
+ print('WARNING: Skipped "{0}": writeable by others.'.format(
+ file_name))
+ else:
+ print('WARNING: Skipped "{0}": owned by someone else.'.format(
+ file_name))
+
+ # Test for obsoleted and/or unknown variables.
+ for name, value in list(user_config.items()):
+ if name.startswith('_'):
+ pass
+ elif hasattr(_Configuration, name):
+ new_type = type(value)
+ old_value = getattr(_Configuration, name)
+ old_type = type(old_value)
+
+ if new_type == old_type or value is None or old_value is None:
# nopep8
+ pass
+ elif new_type is int and (old_type is float or old_type is
bool):
+ pass
+ elif old_type is int and (new_type is float or new_type is
bool):
+ pass
+ elif callable(old_type) and callable(new_type):
+ pass
+ else:
+ print('WARNING: Type of "{0}" changed'.format(name))
+ print(' Was: {0}'.format(old_type))
+ print(' Now: {0}'.format(new_type))
+ else:
+ print('WARNING: Configuration variable {0!r} is defined but '
+ 'unknown. Misspelled?'.format(value))
+
+ for name in _Configuration.__dict__:
+ if name[0] == '_':
+ continue
+ # TODO: Originally it was always copying usernames, sysopnames,
disambiguation_comment
+ if getattr(_Configuration, name) == user_config[name]:
+ continue
+ setattr(self._configuration, name, user_config[name])
+
+ if name in self._configuration._deprecated_variables:
+ print('WARNING: "{0}" is no longer a supported configuration '
+ 'variable.\nPlease inform the maintainers if you depend '
+ 'on it.'.format(name))
+
+ # Fix up default console_encoding
+ if self._configuration.console_encoding is None:
+ if sys.platform == 'win32':
+ self._configuration.console_encoding = 'cp850'
+ else:
+ self._configuration.console_encoding = 'iso-8859-1'
+
+ # Fix up transliteration_target
+ if self._configuration.transliteration_target == 'not set':
+ if sys.platform == 'win32':
+ self._configuration.transliteration_target =
self._configuration.console_encoding
+ print('WARNING: Running on Windows and transliteration_target '
+ 'is not set.')
+ print('Please see https://www.mediawiki.org/wiki/'
+ 'Special:MyLanguage/Manual:Pywikibot/Windows')
+ else:
+ self._configuration.transliteration_target = None
+ elif self._configuration.transliteration_target in ('None', 'none'):
+ self._configuration.transliteration_target = None
+
+ if sys.platform == 'win32' and self._configuration.editor:
+ # single character string literals from
+ #
https://docs.python.org/2/reference/lexical_analysis.html#string-literals
+ # encode('unicode-escape') also changes Unicode characters
+ if set(self._configuration.editor) & set('\a\b\f\n\r\t\v'):
+ print('WARNING: The editor path contains probably invalid '
+ 'escaped characters. Make sure to use a raw-string '
+ '(r"..." or r\'...\'), forward slashs as a path '
+ 'delimiter or to escape the normal path delimiter.')
+
+ # Fix up default site
+ if (self._configuration.family == 'wikipedia' and
+ self._configuration.mylang == 'language'):
+ print('WARNING: family and mylang are not set.\n'
+ 'Defaulting to family="test" and mylang="test".')
+ self._configuration.family = self._configuration.mylang = 'test'
+
+configuration = _ConfigurationWrapper()
+
+
+#
+# When called as main program, list all configuration variables
+#
+def main(*args):
+ import types
+ show_all = True
+ for arg in args[1:]:
+ if arg == 'modified':
+ show_all = False
+ else:
+ print('Unknown arg {0} ignored'.format(arg))
+ configuration._load(None) # TODO: Add -dir:
+ _modified = {} # TODO: Make usable, just dummy
+ for name, value in sorted(configuration._configuration.__dict__):
+ if name[0] != '_':
+ if not type(value) in [types.FunctionType, types.ModuleType]:
+ if show_all or name in _modified:
+ if name in configuration._configuration._private_values
and value:
+ if isinstance(value, dict):
+ value = '{ ...xxxxxxxx... }'
+ elif hasattr(value, '__dict__'):
+ value = '{0}( ...xxxxxxxx... )'.format(
+ value.__class__.__name__)
+ else:
+ value = repr('xxxxxxxx')
+ else:
+ value = repr(value)
+ print('{0}={1}'.format(name, value))
+
+if __name__ == "__main__":
+ main(*sys.argv)
--
To view, visit https://gerrit.wikimedia.org/r/186638
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I4214ec5dd40308f7fc3fa87126175393f1edaf0f
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: XZise <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits