User:FreemBOT/Filters

From Team Fortress Wiki
Jump to: navigation, search

Implement {{Dictionary}}

class DictionaryUpdater:
    def __init__(self):
        self.subpageTemplateLang = """{{#switch:{{{lang|{{SUBPAGENAME}}}}}|%options%}}"""
        self.subpageTemplateParam = """{{#switch:{{{1|}}}|%options%}}"""
        self.invalidParamError = """<span class="error">Error: invalid param.</span>[[Category:ERROR]]"""
        self.subpageTemplateID = """%string%"""
        self.dictionaries = {
            u'Template:Dictionary/advanced': { # Dictionary page
                'name': 'advanced', # Dictionary name (used for categorizing)
                'sync': 'Template:Dictionary/advanced/Special:SyncData' # Page holding last sync data
            },
            u'Template:Dictionary/items': { # Dictionary page
                'name': 'items', # Dictionary name (used for categorizing)
                'sync': 'Template:Dictionary/items/Special:SyncData' # Page holding last sync data
            },
            u'Template:Dictionary/common strings': { # Warning: no underscore
                'name': 'common strings',
                'sync': 'Template:Dictionary/common strings/Special:SyncData'
            },
            u'Template:Dictionary/classes': {
                'name': 'classes',
                'sync': 'Template:Dictionary/classes/Special:SyncData'
            },
            u'Template:Dictionary/demonstrations': {
                'name': 'demonstrations',
                'sync': 'Template:Dictionary/demonstrations/Special:SyncData'
            },
            u'Template:Dictionary/price': {
                'name': 'price',
                'sync': 'Template:Dictionary/price/Special:SyncData',
                'allTemplate': '{{{{{template|item price/fmt}}}|%options%|tt={{{tt|yes}}}}}'
            },
            u'Template:Dictionary/merchandise': {
                'name': 'merchandise',
                'sync': 'Template:Dictionary/merchandise/Special:SyncData'
            },
            u'Template:Dictionary/steam ids': {
                'name': 'steam ids',
                'sync': 'Template:Dictionary/steam ids/Special:SyncData'
            },
            u'Template:Dictionary/achievements/scout': {
                'name': 'achievements/scout',
                'sync': 'Template:Dictionary/achievements/scout/Special:SyncData'
            },
            u'Template:Dictionary/achievements/soldier': {
                'name': 'achievements/soldier',
                'sync': 'Template:Dictionary/achievements/soldier/Special:SyncData'
            },
            u'Template:Dictionary/achievements/pyro': {
                'name': 'achievements/pyro',
                'sync': 'Template:Dictionary/achievements/pyro/Special:SyncData'
            },
            u'Template:Dictionary/achievements/demoman': {
                'name': 'achievements/demoman',
                'sync': 'Template:Dictionary/achievements/demoman/Special:SyncData'
            },
            u'Template:Dictionary/achievements/heavy': {
                'name': 'achievements/heavy',
                'sync': 'Template:Dictionary/achievements/heavy/Special:SyncData'
            },
            u'Template:Dictionary/achievements/engineer': {
                'name': 'achievements/engineer',
                'sync': 'Template:Dictionary/achievements/engineer/Special:SyncData'
            },
            u'Template:Dictionary/achievements/medic': {
                'name': 'achievements/medic',
                'sync': 'Template:Dictionary/achievements/medic/Special:SyncData'
            },
            u'Template:Dictionary/achievements/sniper': {
                'name': 'achievements/sniper',
                'sync': 'Template:Dictionary/achievements/sniper/Special:SyncData'
            },
            u'Template:Dictionary/achievements/spy': {
                'name': 'achievements/spy',
                'sync': 'Template:Dictionary/achievements/spy/Special:SyncData'
            },
            u'Template:Dictionary/achievements/general': {
                'name': 'achievements/general',
                'sync': 'Template:Dictionary/achievements/general/Special:SyncData'
            },
            u'Template:Dictionary/achievements/halloween': {
                'name': 'achievements/halloween',
                'sync': 'Template:Dictionary/achievements/halloween/Special:SyncData'
            },
            u'Template:Dictionary/achievements/treasure hunt': {
                'name': 'achievements/treasure hunt',
                'sync': 'Template:Dictionary/achievements/treasure hunt/Special:SyncData'
            },
            u'Template:Dictionary/achievements/replay': {
                'name': 'achievements/replay',
                'sync': 'Template:Dictionary/achievements/replay/Special:SyncData'
            },
            u'Template:Dictionary/achievements/summer camp': {
                'name': 'achievements/summer camp',
                'sync': 'Template:Dictionary/achievements/summer camp/Special:SyncData'
            },
            u'Template:Dictionary/achievements/foundry': {
                'name': 'achievements/foundry',
                'sync': 'Template:Dictionary/achievements/foundry/Special:SyncData'
            },
            u'Template:Dictionary/achievements/christmas': {
                'name': 'achievements/christmas',
                'sync': 'Template:Dictionary/achievements/christmas/Special:SyncData'
            },
            u'Template:Dictionary/blueprints': {
                'name': 'blueprints',
                'sync': 'Template:Dictionary/blueprints/Special:SyncData'
            },
            u'Template:Dictionary/templatecore': {
                'name': 'templatecore',
                'sync': 'Template:Dictionary/templatecore/Special:SyncData'
            },
            u'Template:Dictionary/defindex': {
                'name': 'defindex',
                'sync': 'Template:Dictionary/defindex/Special:SyncData'
            },
            u'Template:Dictionary/quad': {
                'name': 'quad',
                'sync': 'Template:Dictionary/quad/Special:SyncData',
                'blankString': '-'
            },
            u'Template:Dictionary/item set weapons': {
                'name': 'item set weapons',
                'sync': 'Template:Dictionary/item set weapons/Special:SyncData'
            }
        }
        self.subpageSeparator = u'/'
        # List of supported languages, in prefered order
        self.languages = [u'en', u'ar', u'cs', u'da', u'de', u'es', u'fi', u'fr', u'hu', u'it', u'ja', u'ko', u'nl', u'no', u'pl', u'pt', u'pt-br', u'ro', u'ru', u'sv', u'tr', u'zh-hans', u'zh-hant']
        self.defaultLang = u'en'
        self.allKeyName = u'_all_'
        self.filterName = u'Your friendly neighborhood dictionary updater'
        self.commentsExtract = compileRegex(r)
        self.stringsExtract = compileRegex(r'(?:^[ \t]*#[ \t]*([^\r\n]*?)[ \t]*$\s*)?^[ \t]*([^\r\n]+?[ \t]*(?:\|[ \t]*[^\r\n]+?[ \t]*)*):[ \t]*([^\r\n]+?[ \t]*$|\s*[\r\n]+(?:\s*[ \t]+[-\w]+[ \t]*:[ \t]*[^\r\n]+[ \t]*$)+)', re.IGNORECASE | re.MULTILINE)
        self.translationExtract = compileRegex(r'^[ \t]+([-\w]+)[ \t]*:[ \t]*([^\r\n]+)[ \t]*$', re.IGNORECASE | re.MULTILINE)
        self.scheduler = BatchScheduler(16)
        addWhitelistPage(self.dictionaries.keys())
    def generateSubpage(self, keyName, data, currentDict, syncData):
        h = hashlib.md5()
        if type(data) is type({}): # Subkeys (translations or not)
            isTranslation = True
            subpage = u(self.subpageTemplateLang)
            for k in data:
                if 'blankString' in self.dictionaries[currentDict] and data[k] == self.dictionaries[currentDict]['blankString']:
                    data[k] = u
                if isTranslation and k not in self.languages:
                    isTranslation = False
                    subpage = u(self.subpageTemplateParam)
            ordered = []
            unordered = {}
            if isTranslation:
                missing = []
                for lang in self.languages:
                    if lang in data:
                        ordered.append(lang + u'=' + data[lang])
                        unordered[lang] = data[lang]
                        h.update((lang + u'=' + data[lang]).encode('utf8'))
                    else:
                        missing.append(lang)
                        h.update((u'null-' + lang).encode('utf8'))
                if self.defaultLang in data:
                    ordered.insert(0, u'#default=' + data[self.defaultLang])
                if len(missing):
                    subpage = subpage.replace(u'%missing%', u"Languages missing: " + u', '.join(missing))
                else:
                    subpage = subpage.replace(u'%missing%', u"Supported languages: all")
            else: # Not a translation
                h.update('Any-')
                subkeys = data.keys()
                subkeys.sort()
                for k in subkeys:
                    ordered.append(k + u'=' + data[k])
                    unordered[k] = data[k]
                    h.update((k + u'=' + data[k]).encode('utf8'))
            if 'allTemplate' in self.dictionaries[currentDict] and (len(unordered) or len(self.dictionaries[currentDict]['allTemplate']['params'])):
                allKey = []
                keys = unordered.keys()
                keys.sort()
                for k in keys:
                    allKey.append(k + u'=' + unordered[k])
                insertIndex = 0
                if isTranslation and self.defaultLang in data:
                    insertIndex = 1
                ordered.insert(insertIndex, u(self.allKeyName) + u'=' + u(self.dictionaries[currentDict]['allTemplate'].replace(u'%options%', u'|'.join(allKey))))
            subpage = subpage.replace(u'%options%', u'|'.join(ordered))
        else: # No subkeys
            data = u(data)
            subpage = self.subpageTemplateID
            h.update(u(u'ID-' + data).encode('utf8'))
            subpage = subpage.replace(u'%string%', data)
        h = u(h.hexdigest())
        if keyName in syncData and syncData[keyName] == h:
            return # Same hash
        syncData[keyName] = h # Update sync data
        subpage = subpage.replace(u'%dictionary%', currentDict)
        subpage = subpage.replace(u'%dictionaryname%', self.dictionaries[currentDict]['name'])
        subpage = subpage.replace(u'%keyname%', keyName)
        self.scheduler.schedule(editPage, currentDict + self.subpageSeparator + keyName, subpage, summary=u'Pushed changes from [[:' + currentDict + u']] for string "' + keyName + u'".', minor=True, nocreate=False)
    def processComment(self, commentString, currentDict, definedStrings, syncData):
        commentContents = []
        for extractedStr in self.stringsExtract.finditer(commentString):
            comment = u
            if extractedStr.group(1):
                comment = u'# ' + u(extractedStr.group(1)) + u'\n'
            dataString = u(extractedStr.group(3))
            if dataString.find(u'\r') == -1 and dataString.find(u'\n') == -1: # Assume no subkeys
                data = dataString.strip()
                dataWriteback = u' ' + data
            else: # There's subkeys; detect whether this is a translation or not
                data = {}
                isTranslation = True
                for translation in self.translationExtract.finditer(dataString.rstrip()):
                    data[u(translation.group(1))] = u(translation.group(2))
                    if u(translation.group(1)) not in self.languages:
                        isTranslation = False
                ordered = []
                if isTranslation:
                    for lang in self.languages:
                        if lang in data:
                            ordered.append(u'  ' + lang + u': ' + data[lang])
                else: # Not a translation, so order in alphabetical order
                    subkeys = data.keys()
                    subkeys.sort()
                    for subk in subkeys:
                        ordered.append(u'  ' + subk + u': ' + data[subk])
                dataWriteback = u'\n' + u'\n'.join(ordered)
            keyNames = u(extractedStr.group(2)).lower().split(u'|')
            validKeyNames = []
            for keyName in keyNames:
                keyName = keyName.replace(u'_', u' ').strip()
                if keyName in definedStrings:
                    continue # Duplicate key
                definedStrings.append(keyName)
                validKeyNames.append(keyName)
                self.generateSubpage(keyName, data, currentDict, syncData)
            if len(validKeyNames):
                commentContents.append(comment + u' | '.join(validKeyNames) + u':' + dataWriteback)
        self.scheduler.execute()
        return u'\n\n'.join(commentContents)
    def __call__(self, content, **kwargs):
        if 'article' not in kwargs:
            return content
        if u(kwargs['article'].title) not in self.dictionaries:
            return content
        currentDict = u(kwargs['article'].title)
        syncPage = page(self.dictionaries[currentDict]['sync'])
        try:
            syncDataText = u(syncPage.getWikiText()).split(u'\n')
        except: # Page probably doesn't exist
            syncDataText = u
        syncData = {}
        for sync in syncDataText:
            sync = u(sync.strip())
            if not sync:
                continue
            sync = sync.split(u':', 2)
            if len(sync) == 2:
                syncData[sync[0]] = sync[1]
        oldSyncData = syncData.copy()
        newContent = u
        previousIndex = 0
        definedStrings = []
        for comment in self.commentsExtract.finditer(content):
            newContent += content[previousIndex:comment.start()]
            previousIndex = comment.end()
            # Process current comment
            newContent += u
        newContent += content[previousIndex:]
        # Check if we need to update sync data
        needUpdate = False
        for k in syncData:
            if k not in oldSyncData or oldSyncData[k] != syncData[k]:
                needUpdate = True
                break
        # Check for deleted strings
        for k in oldSyncData:
            if k not in definedStrings:
                try:
                    deletePage(currentDict + self.subpageSeparator + k, 'Removed deleted string "' + k + u'" from ' + currentDict + u'.')
                except:
                    pass
                if k in syncData:
                    del syncData[k]
                needUpdate = True
        if needUpdate:
            # Build syncdata string representation
            syncKeys = syncData.keys()
            syncKeys.sort()
            syncLines = []
            for k in syncKeys:
                syncLines.append(k + u':' + syncData[k])
            editPage(syncPage, u'\n'.join(syncLines), summary=u'Updated synchronization information for [[:' + currentDict + u']].', minor=True, nocreate=False)
        return newContent
    def scheduledRun(self):
        for d in self.dictionaries:
            fixPage(d)
dictUpdater = DictionaryUpdater()
addFilter(dictUpdater)
scheduleTask(dictUpdater.scheduledRun, 3)