23 from .DiffHighlighter import DiffHighlighter |
23 from .DiffHighlighter import DiffHighlighter |
24 |
24 |
25 import Utilities |
25 import Utilities |
26 import Preferences |
26 import Preferences |
27 |
27 |
28 from difflib import SequenceMatcher |
28 from difflib import unified_diff, context_diff |
29 |
|
30 |
|
31 def unified_diff(a, b, fromfile='', tofile='', fromfiledate='', |
|
32 tofiledate='', n=3, lineterm='\n'): |
|
33 """ |
|
34 Compare two sequences of lines; generate the delta as a unified diff. |
|
35 |
|
36 Unified diffs are a compact way of showing line changes and a few |
|
37 lines of context. The number of context lines is set by 'n' which |
|
38 defaults to three. |
|
39 |
|
40 By default, the diff control lines (those with ---, +++, or @@) are |
|
41 created with a trailing newline. This is helpful so that inputs |
|
42 created from file.readlines() result in diffs that are suitable for |
|
43 file.writelines() since both the inputs and outputs have trailing |
|
44 newlines. |
|
45 |
|
46 For inputs that do not have trailing newlines, set the lineterm |
|
47 argument to "" so that the output will be uniformly newline free. |
|
48 |
|
49 The unidiff format normally has a header for filenames and modification |
|
50 times. Any or all of these may be specified using strings for |
|
51 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. The modification |
|
52 times are normally expressed in the format returned by time.ctime(). |
|
53 |
|
54 Example: |
|
55 |
|
56 <pre> |
|
57 >>> for line in unified_diff('one two three four'.split(), |
|
58 ... 'zero one tree four'.split(), 'Original', 'Current', |
|
59 ... 'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:20:52 2003', |
|
60 ... lineterm=''): |
|
61 ... print line |
|
62 --- Original Sat Jan 26 23:30:50 1991 |
|
63 +++ Current Fri Jun 06 10:20:52 2003 |
|
64 @@ -1,4 +1,4 @@ |
|
65 +zero |
|
66 one |
|
67 -two |
|
68 -three |
|
69 +tree |
|
70 four |
|
71 </pre> |
|
72 |
|
73 @param a first sequence of lines (list of strings) |
|
74 @param b second sequence of lines (list of strings) |
|
75 @param fromfile filename of the first file (string) |
|
76 @param tofile filename of the second file (string) |
|
77 @param fromfiledate modification time of the first file (string) |
|
78 @param tofiledate modification time of the second file (string) |
|
79 @param n number of lines of context (integer) |
|
80 @param lineterm line termination string (string) |
|
81 @return a generator yielding lines of differences |
|
82 """ |
|
83 started = False |
|
84 for group in SequenceMatcher(None, a, b).get_grouped_opcodes(n): |
|
85 if not started: |
|
86 yield '--- {0}\t{1}{2}'.format(fromfile, fromfiledate, lineterm) |
|
87 yield '+++ {0}\t{1}{2}'.format(tofile, tofiledate, lineterm) |
|
88 started = True |
|
89 i1 = group[0][1] |
|
90 i2 = group[-1][2] |
|
91 j1 = group[0][3] |
|
92 j2 = group[-1][4] |
|
93 yield "@@ -{0:d},{1:d} +{2:d},{3:d} @@{4}".format( |
|
94 i1 + 1, i2 - i1, j1 + 1, j2 - j1, lineterm) |
|
95 for tag, i1, i2, j1, j2 in group: |
|
96 if tag == 'equal': |
|
97 for line in a[i1:i2]: |
|
98 yield ' ' + line |
|
99 continue |
|
100 if tag == 'replace' or tag == 'delete': |
|
101 for line in a[i1:i2]: |
|
102 yield '-' + line |
|
103 if tag == 'replace' or tag == 'insert': |
|
104 for line in b[j1:j2]: |
|
105 yield '+' + line |
|
106 |
|
107 |
|
108 def context_diff(a, b, fromfile='', tofile='', |
|
109 fromfiledate='', tofiledate='', n=3, lineterm='\n'): |
|
110 r""" |
|
111 Compare two sequences of lines; generate the delta as a context diff. |
|
112 |
|
113 Context diffs are a compact way of showing line changes and a few |
|
114 lines of context. The number of context lines is set by 'n' which |
|
115 defaults to three. |
|
116 |
|
117 By default, the diff control lines (those with *** or ---) are |
|
118 created with a trailing newline. This is helpful so that inputs |
|
119 created from file.readlines() result in diffs that are suitable for |
|
120 file.writelines() since both the inputs and outputs have trailing |
|
121 newlines. |
|
122 |
|
123 For inputs that do not have trailing newlines, set the lineterm |
|
124 argument to "" so that the output will be uniformly newline free. |
|
125 |
|
126 The context diff format normally has a header for filenames and |
|
127 modification times. Any or all of these may be specified using |
|
128 strings for 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. |
|
129 The modification times are normally expressed in the format returned |
|
130 by time.ctime(). If not specified, the strings default to blanks. |
|
131 |
|
132 Example: |
|
133 |
|
134 <pre> |
|
135 >>> print ''.join( |
|
136 ... context_diff('one\ntwo\nthree\nfour\n'.splitlines(1), |
|
137 ... 'zero\none\ntree\nfour\n'.splitlines(1), 'Original', 'Current', |
|
138 ... 'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:22:46 2003')), |
|
139 *** Original Sat Jan 26 23:30:50 1991 |
|
140 --- Current Fri Jun 06 10:22:46 2003 |
|
141 *************** |
|
142 *** 1,4 **** |
|
143 one |
|
144 ! two |
|
145 ! three |
|
146 four |
|
147 --- 1,4 ---- |
|
148 + zero |
|
149 one |
|
150 ! tree |
|
151 four |
|
152 </pre> |
|
153 |
|
154 @param a first sequence of lines (list of strings) |
|
155 @param b second sequence of lines (list of strings) |
|
156 @param fromfile filename of the first file (string) |
|
157 @param tofile filename of the second file (string) |
|
158 @param fromfiledate modification time of the first file (string) |
|
159 @param tofiledate modification time of the second file (string) |
|
160 @param n number of lines of context (integer) |
|
161 @param lineterm line termination string (string) |
|
162 @return a generator yielding lines of differences |
|
163 """ |
|
164 started = False |
|
165 prefixmap = {'insert': '+ ', 'delete': '- ', 'replace': '! ', |
|
166 'equal': ' '} |
|
167 for group in SequenceMatcher(None, a, b).get_grouped_opcodes(n): |
|
168 if not started: |
|
169 yield '*** {0}\t{1}{2}'.format(fromfile, fromfiledate, lineterm) |
|
170 yield '--- {0}\t{1}{2}'.format(tofile, tofiledate, lineterm) |
|
171 started = True |
|
172 |
|
173 yield '***************{0}'.format(lineterm) |
|
174 if group[-1][2] - group[0][1] >= 2: |
|
175 yield '*** {0:d},{1:d} ****{2}'.format( |
|
176 group[0][1] + 1, group[-1][2], lineterm) |
|
177 else: |
|
178 yield '*** {0:d} ****{1}'.format(group[-1][2], lineterm) |
|
179 visiblechanges = [e for e in group if e[0] in ('replace', 'delete')] |
|
180 if visiblechanges: |
|
181 for tag, i1, i2, _, _ in group: |
|
182 if tag != 'insert': |
|
183 for line in a[i1:i2]: |
|
184 yield prefixmap[tag] + line |
|
185 |
|
186 if group[-1][4] - group[0][3] >= 2: |
|
187 yield '--- {0:d},{1:d} ----{2}'.format( |
|
188 group[0][3] + 1, group[-1][4], lineterm) |
|
189 else: |
|
190 yield '--- {0:d} ----{1}'.format(group[-1][4], lineterm) |
|
191 visiblechanges = [e for e in group if e[0] in ('replace', 'insert')] |
|
192 if visiblechanges: |
|
193 for tag, _, _, j1, j2 in group: |
|
194 if tag != 'delete': |
|
195 for line in b[j1:j2]: |
|
196 yield prefixmap[tag] + line |
|
197 |
29 |
198 |
30 |
199 class DiffDialog(QWidget, Ui_DiffDialog): |
31 class DiffDialog(QWidget, Ui_DiffDialog): |
200 """ |
32 """ |
201 Class implementing a dialog to compare two files. |
33 Class implementing a dialog to compare two files. |