def showDiff(oldtext, newtext, context=0): """ Output a string showing the differences between oldtext and newtext. The differences are highlighted (only on compatible systems) to show which changes were made. """ PatchManager(oldtext, newtext, context=context).print_hunks()
def _convert_revisions_to_diffs( revisions: List[Revision]) -> List[RevisionDiff]: if len(revisions) <= 1: return [] result = [] revisions.sort(key=lambda val: val.timestamp) for i in range(1, len(revisions)): old_rev = revisions[i - 1] new_rev = revisions[i] pm = PatchManager(old_rev.slots['main']['*'], new_rev.slots['main']['*']) rev_diff = RevisionDiff.of_hunks(revisions[i - 1].revid, revisions[i].revid, revisions[i - 1].timestamp, revisions[i].timestamp, pm.hunks) result.append(rev_diff) return result
def test_patch_manager_no_diff(self): """Test PatchManager for the same strings.""" for context in range(2): p = PatchManager('Pywikibot', 'Pywikibot', context=context) with self.subTest(context=context): self.assertIsEmpty(p.hunks)
def run(self): self.page = pywikibot.Page(self.site, self.change['title'], ns=self.change['namespace']) self.output(u"Handling") if self.page.isRedirectPage(): self.output(u"Redirect") return if self.page.namespace() == 4: # Project pages needs attention (__NEWSECTIONLINK__) if not self.isDiscussion(self.page): self.output(u"Not a discussion") return user = pywikibot.User(self.site, self.change['user']) if self.isOptout(user): self.output(u"%s opted-out" % user) return # diff-reading. if self.change['type'] == 'new': old_text = '' else: old_text = self.page.getOldVersion(self.change['revision']['old']) new_text = self.page.getOldVersion(self.change['revision']['new']) if "{{speedy" in new_text.lower(): self.output(u"{{speedy -- ignored") return diff = PatchManager(old_text.split("\n") if old_text else [], new_text.split("\n"), by_letter=True) diff.print_hunks() tosignstr = False tosignnum = False for block in diff.blocks: if block[0] < 0: continue hunk = diff.hunks[block[0]] group = hunk.group # print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" % # (block[0], block[1][0], block[1][1], hunk.a[block[1][0]:block[1][1]], block[2][0], block[2][1], hunk.b[block[2][0]:block[2][1]])) for tag, i1, i2, j1, j2 in group: # print ("%7s a[%d:%d] (%s) b[%d:%d] (%s)" % # (tag, i1, i2, hunk.a[i1:i2], j1, j2, hunk.b[j1:j2])) if tag == 'insert': for j in range(j1, j2): line = hunk.b[j] if self.page == user.getUserTalkPage( ) or self.page.title().startswith( user.getUserTalkPage().title() + '/'): if "{{" in line.lower(): self.output( u"User adding templates to his own talk page -- ignored" ) return excluderegextest = self.matchExcludeRegex(line) if excluderegextest is not None: self.output(u"%s -- ignored" % excluderegextest) return if self.isComment(line): tosignnum = j tosignstr = line if self.isSigned(user, tosignstr): self.output(u"Signed") return if tosignstr is False: self.output(u"No inserts") return if self.isSigned(user, tosignstr): self.output(u"Signed") return if not self.isFreqpage(self.page): self.output(u"Waiting") time.sleep(60) pass currenttext = self.page.get(force=True).split("\n") if currenttext[tosignnum] == tosignstr: currenttext[tosignnum] += self.getSignature(tosignstr, user) elif currenttext.count(tosignstr) == 1: currenttext[currenttext.index(tosignstr)] += self.getSignature( tosignstr, user) else: self.output(u"Line no longer found, probably signed") return summary = u'Signing comment by %s - "%s"' % (self.userlink(user), self.change['comment']) self.userPut(self.page, self.page.get(), u"\n".join(currenttext), comment=summary) # self.notify(user) {{subst:Please sign}} -- ignore {{bots}} if self.controller.checknotify(user): self.output(u"Notifying %s" % user) talk = user.getUserTalkPage() if talk.isRedirectPage(): talk = talk.getRedirectTarget() try: talktext = talk.get(force=True, get_redirect=True) + u'\n\n' except pywikibot.NoPage: talktext = '' talktext += u'{{subst:Please sign}} --~~~~' self.userPut( talk, talk.text, talktext, comment= u"Added {{subst:[[Template:Please sign|Please sign]]}} note.", minor=False)
def changeShouldBeHandled(self) -> Tuple[bool, Optional[ShouldBeHandledResult]]: self.page = pywikibot.Page(self.site, self.revInfo.title, ns=self.revInfo.namespace) self.output("Handling") if self.isPageOptOut(): self.output("Page %s on opt-out list" % self.page.title()) return False, None if ( self.page.title().find("/Archiv/") > 0 or self.page.title().find("/Archiv ") > 0 or self.page.title().endswith("/Archiv") ): self.output("Suspected archive page") return False, None if self.page.title().startswith("Portal Diskussion:") and ( self.page.title().endswith("/Artikel des Monats") or self.page.title().endswith("/Neue Artikel") ): return False, None if self.page.isRedirectPage(): self.output("Redirect") return False, None if self.page.namespace() == 4: # Project pages needs attention (__NEWSECTIONLINK__) if not self.isDiscussion(): self.output("Not a discussion") return False, None if {"mw-undo", "mw-rollback"}.intersection(self.getTags()): self.output("undo / rollback") return False, None user = pywikibot.User(self.site, self.revInfo.user) if self.isUserOptOut(user.username): self.output("%s opted-out" % user.username) return False, None if ( self.page.namespace() == pywikibot.site.Namespace.USER_TALK and ( self.page.title(with_ns=False) == user.username or self.page.title(with_ns=False).startswith(user.username + "/") ) and self.controller.isExperiencedUser(user) ): self.output("Experienced user %s edited own talk page" % user.username) return False, None # diff-reading. if self.revInfo.type == "new": old_text = "" else: assert self.revInfo.oldRevision is not None try: old_text = self.page.getOldVersion(self.revInfo.oldRevision) except KeyError: self.warning("Old revision %d not found, retrying..." % self.revInfo.oldRevision) time.sleep(10) old_text = self.page.getOldVersion(self.revInfo.oldRevision) try: new_text = self.page.getOldVersion(self.revInfo.newRevision) except KeyError: self.warning("New revision %d not found, retrying..." % self.revInfo.newRevision) time.sleep(10) new_text = self.page.getOldVersion(self.revInfo.newRevision) new_lines = new_text.split("\n") diff = PatchManager(old_text.split("\n") if old_text else [], new_lines, by_letter=True) # diff.print_hunks() tosignstr = "" tosignnum = -1 if len(diff.hunks) > 1: self.output("Multiple diff hunks %d" % len(diff.blocks)) return False, None hunk = diff.hunks[0] group = hunk.group timestamp1 = self.getSignatureTimestampString(self.revInfo.timestamp) timestamp2 = self.getSignatureTimestampString(self.revInfo.timestamp - 60) exactTimeSigned = False timeSigned = False userSigned = False signatureTimestampCount = 0 for tag, _, _, j1, j2 in group: if tag == "insert": insertStartLine = j1 for j in range(j1, j2): line = hunk.b[j] if self.page == user.getUserTalkPage() or self.page.title().startswith( user.getUserTalkPage().title() + "/" ): if "{{" in line.lower(): self.output("User adding templates to their " "own talk page -- ignored") return False, None excluderegextest = self.matchExcludeRegex(line) if excluderegextest is not None: self.output("Matches %s -- ignored" % excluderegextest) return False, None if self.isNotExcludedLine(line): tosignnum = j tosignstr = line exactTimeSigned = tosignstr.find(timestamp1) >= 0 or tosignstr.find(timestamp2) >= 0 lineTimeSigned = self.hasAnySignatureTimestamp(line) if lineTimeSigned: signatureTimestampCount += 1 timeSigned = timeSigned or lineTimeSigned userSigned = userSigned or self.isUserSigned(user, tosignstr) if timeSigned and userSigned: self.controller.clearnotify(user) self.output("Already signed") return False, None if tag == "delete": self.output("Line deletion found") return False, None if tag == "replace": self.output("Line replacement found") return False, None if not tosignstr: self.output("No inserts") return False, None if signatureTimestampCount > 1: self.output("Multiple timestamps found") return False, None if self.hasAnySignatureAllowedUserLink(tosignstr) and timeSigned and self.controller.isExperiencedUser(user): self.output("Timestamp and other user link found - likely copied") return False, None if not timeSigned and not userSigned: if self.isAlreadySignedInFollowingLines(user, new_lines, tosignnum): return False, None if not timeSigned and not userSigned and self.isPostscriptum(tosignstr) and tosignnum > 1: checkLineNo = tosignnum - 1 if not new_lines[checkLineNo].strip() and checkLineNo > 0: checkLineNo -= 1 if self.isUserSigned(user, new_lines[checkLineNo]) and self.hasAnySignatureTimestamp( new_lines[checkLineNo] ): self.output("Postcriptum found") return False, None if not timeSigned: precedingSignatureOrSectionFound = False for i in range(0, insertStartLine): if new_lines[i].strip().startswith("=="): precedingSignatureOrSectionFound = True break if self.hasAnySignature(new_lines[i]): precedingSignatureOrSectionFound = True break followingSignatureOrSectionFound = False for i in range(tosignnum + 1, len(new_lines)): if new_lines[i].strip().startswith("=="): followingSignatureOrSectionFound = True break if self.hasAnySignature(new_lines[i]): followingSignatureOrSectionFound = True break if not precedingSignatureOrSectionFound: if ( new_lines[insertStartLine].strip().startswith("{{") or tosignstr.strip().startswith("{{") ) and tosignstr.strip().endswith("}}"): self.output("Insertion of template at beginning of page") return False, None if followingSignatureOrSectionFound and self.controller.isExperiencedUser(user): self.output( "Insertion by experienced user at the beginning of talk page before any sections or signatures" ) return False, None if self.hasApplicableNobotsTemplate(new_lines, insertStartLine): return False, None # if not user-signed don't consider if not userSigned and timeSigned and not exactTimeSigned: timeSigned = False # all checks passed return True, ShouldBeHandledResult(tosignnum, tosignstr, timeSigned, userSigned)