jenkins-bot has submitted this change and it was merged. Change subject: [FEAT] Add context to diffs ......................................................................
[FEAT] Add context to diffs This adds context to printed diffs which can be any number of lines given. Change-Id: I6d212fd5912281fb6f0225a7657a1edb56f4b8d9 --- M pywikibot/__init__.py M pywikibot/diff.py 2 files changed, 104 insertions(+), 13 deletions(-) Approvals: John Vandenberg: Looks good to me, approved jenkins-bot: Verified diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py index d6ea7ec..615589e 100644 --- a/pywikibot/__init__.py +++ b/pywikibot/__init__.py @@ -646,14 +646,14 @@ config.default_edit_summary = s -def showDiff(oldtext, newtext): +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).print_hunks() + PatchManager(oldtext, newtext, context=context).print_hunks() # Throttle and thread handling diff --git a/pywikibot/diff.py b/pywikibot/diff.py index 015dcbd..79c8446 100644 --- a/pywikibot/diff.py +++ b/pywikibot/diff.py @@ -12,10 +12,13 @@ import difflib import sys + +from collections import Sequence if sys.version_info[0] > 2: from itertools import zip_longest else: from itertools import izip_longest as zip_longest + try: from bs4 import BeautifulSoup except ImportError as bserror: @@ -23,6 +26,7 @@ import pywikibot from pywikibot.backports import format_range_unified # introduced in 2.7.2 +from pywikibot.tools import deprecated_args class Hunk(object): @@ -72,9 +76,14 @@ def get_header(self): """Provide header of unified diff.""" - a_rng = format_range_unified(*self.a_rng) - b_rng = format_range_unified(*self.b_rng) - return '@@ -{0} +{1} @@\n'.format(a_rng, b_rng) + return self.get_header_text(self.a_rng, self.b_rng) + '\n' + + @staticmethod + def get_header_text(a_rng, b_rng, affix='@@'): + """Provide header for any ranges.""" + a_rng = format_range_unified(*a_rng) + b_rng = format_range_unified(*b_rng) + return '{0} -{1} +{2} {0}'.format(affix, a_rng, b_rng) def create_diff(self): """Generator of diff text for this hunk, without formatting.""" @@ -174,6 +183,22 @@ % (self.__class__.__name__, self.group) +class _SuperHunk(Sequence): + + def __init__(self, hunks): + self._hunks = hunks + self.a_rng = (self._hunks[0].a_rng[0], self._hunks[-1].a_rng[1]) + self.b_rng = (self._hunks[0].b_rng[0], self._hunks[-1].b_rng[1]) + self.pre_context = self._hunks[0].pre_context + self.post_context = self._hunks[0].post_context + + def __getitem__(self, idx): + return self._hunks[idx] + + def __len__(self): + return len(self._hunks) + + class PatchManager(object): """Apply patches to text_a to obtain a new text. @@ -181,15 +206,16 @@ If all hunks are approved, text_b will be obtained. """ - def __init__(self, text_a, text_b, n=0, by_letter=False): + @deprecated_args(n='context') + def __init__(self, text_a, text_b, context=0, by_letter=False): """Constructor. @param text_a: base text @type text_a: basestring @param text_b: target text @type text_b: basestring - @param n: line of context as defined in difflib.get_grouped_opcodes(). - @type n: int + @param context: number of lines which are context + @type context: int @param by_letter: if text_a and text_b are single lines, comparison can be done letter by letter. @type by_letter: bool @@ -207,11 +233,24 @@ # groups and hunk have same order (one hunk correspond to one group). s = difflib.SequenceMatcher(None, self.a, self.b) - self.groups = list(s.get_grouped_opcodes(n)) - self.hunks = [Hunk(self.a, self.b, group) for group in self.groups] + self.groups = list(s.get_grouped_opcodes(0)) + self.hunks = [] + previous_hunk = None + for group in self.groups: + hunk = Hunk(self.a, self.b, group) + self.hunks.append(hunk) + hunk.pre_context = hunk.a_rng[0] + if previous_hunk: + hunk.pre_context -= previous_hunk.a_rng[1] + previous_hunk.post_context = hunk.pre_context + previous_hunk = hunk + if self.hunks: + self.hunks[-1].post_context = len(self.a) - self.hunks[-1].a_rng[1] # blocks are a superset of hunk, as include also parts not # included in any hunk. self.blocks = self.get_blocks() + self.context = context + self._super_hunks = self._generate_super_hunks() def get_blocks(self): """Return list with blocks of indexes which compose a and, where applicable, b. @@ -246,8 +285,60 @@ def print_hunks(self): """Print the headers and diff texts of all hunks to the output.""" - for hunk in self.hunks: - pywikibot.output(hunk.header + hunk.diff_text) + if self.hunks: + pywikibot.output('\n'.join(self._generate_diff(super_hunk) + for super_hunk in self._super_hunks)) + + def _generate_super_hunks(self, hunks=None): + if hunks is None: + hunks = self.hunks + if self.context: + # Determine if two hunks are connected by self.context + super_hunk = [] + super_hunks = [super_hunk] + for hunk in hunks: + # self.context * 2, because if self.context is 2 the hunks would be + # directly adjacent when 4 lines in between and for anything + # below 4 they share lines. + # not super_hunk == first hunk as any other super_hunk is + # created with one hunk + if (not super_hunk or + hunk.pre_context <= self.context * 2): + # previous hunk has shared/adjacent self.context lines + super_hunk += [hunk] + else: + super_hunk = [hunk] + super_hunks += [super_hunk] + else: + super_hunks = [[hunk] for hunk in hunks] + return [_SuperHunk(sh) for sh in super_hunks] + + def _get_context_range(self, super_hunk): + """Dynamically determine context range for a super hunk.""" + return ((super_hunk.a_rng[0] - min(super_hunk.pre_context, self.context), + super_hunk.a_rng[1] + min(super_hunk.post_context, self.context)), + (super_hunk.b_rng[0] - min(super_hunk.pre_context, self.context), + super_hunk.b_rng[1] + min(super_hunk.post_context, self.context))) + + def _generate_diff(self, hunks): + """Generate a diff text for the given hunks.""" + def extend_context(start, end): + """Add context lines.""" + return ''.join(' {0}\n'.format(line.rstrip()) + for line in self.a[start:end]) + + context_range = self._get_context_range(hunks) + + output = ('\03{aqua}' + + Hunk.get_header_text(*context_range) + '\03{default}\n' + + extend_context(context_range[0][0], hunks[0].a_rng[0])) + previous_hunk = None + for hunk in hunks: + if previous_hunk: + output += extend_context(previous_hunk.a_rng[1], hunk.a_rng[0]) + previous_hunk = hunk + output += hunk.diff_text + return output + extend_context(hunks[-1].a_rng[1], context_range[0][1]) def review_hunks(self): """Review hunks.""" @@ -275,7 +366,7 @@ hunk = pending.pop(0) - pywikibot.output(hunk.header + hunk.diff_text) + pywikibot.output(self._generate_diff(_SuperHunk([hunk]))) choice = pywikibot.input_choice(question, answers, default='r', automatic_quit=False) -- To view, visit https://gerrit.wikimedia.org/r/210704 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I6d212fd5912281fb6f0225a7657a1edb56f4b8d9 Gerrit-PatchSet: 5 Gerrit-Project: pywikibot/core Gerrit-Branch: master Gerrit-Owner: XZise <commodorefabia...@gmx.de> Gerrit-Reviewer: John Vandenberg <jay...@gmail.com> Gerrit-Reviewer: Ladsgroup <ladsgr...@gmail.com> Gerrit-Reviewer: Merlijn van Deen <valhall...@arctus.nl> Gerrit-Reviewer: XZise <commodorefabia...@gmx.de> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ Pywikibot-commits mailing list Pywikibot-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/pywikibot-commits