Method: difflib._mdiff
Calls: 18, Exceptions: 8, Paths: 8Back
Path 1: 10 calls (0.56)
['', ' 1. Beautiful is beTTer than ugly.', ' 2. Explicit is better than implicit.', ' 3. Simple is better than complex.', ' 4. Complex is bett...
['line 0', '1234567890123456789012345689012345', 'line 1', 'line 2 added', 'line 3', 'line 4 chanGEd', 'line 5a chanGed', 'line 6a changEd', 'l...
None (10)
None (10)
IS_CHARACTER_JUNK def (10)
tuple (155) None (10)
1def _mdiff(fromlines, tolines, context=None, linejunk=None,
2 charjunk=IS_CHARACTER_JUNK):
3 r"""Returns generator yielding marked up from/to side by side differences.
4
5 Arguments:
6 fromlines -- list of text lines to compared to tolines
7 tolines -- list of text lines to be compared to fromlines
8 context -- number of context lines to display on each side of difference,
9 if None, all from/to text lines will be generated.
10 linejunk -- passed on to ndiff (see ndiff documentation)
11 charjunk -- passed on to ndiff (see ndiff documentation)
12
13 This function returns an iterator which returns a tuple:
14 (from line tuple, to line tuple, boolean flag)
15
16 from/to line tuple -- (line num, line text)
17 line num -- integer or None (to indicate a context separation)
18 line text -- original line text with following markers inserted:
19 '\0+' -- marks start of added text
20 '\0-' -- marks start of deleted text
21 '\0^' -- marks start of changed text
22 '\1' -- marks end of added/deleted/changed text
23
24 boolean flag -- None indicates context separation, True indicates
25 either "from" or "to" line contains a change, otherwise False.
26
27 This function/iterator was originally developed to generate side by side
28 file difference for making HTML pages (see HtmlDiff class for example
29 usage).
30
31 Note, this function utilizes the ndiff function to generate the side by
32 side difference markup. Optional ndiff arguments may be passed to this
33 function and they in turn will be passed to ndiff.
34 """
35 import re
36
37 # regular expression for finding intraline change indices
38 change_re = re.compile(r'(\++|\-+|\^+)')
39
40 # create the difference iterator to generate the differences
41 diff_lines_iterator = ndiff(fromlines,tolines,linejunk,charjunk)
42
43 def _make_line(lines, format_key, side, num_lines=[0,0]):
44 """Returns line of text with user's change markup and line formatting.
45
46 lines -- list of lines from the ndiff generator to produce a line of
47 text from. When producing the line of text to return, the
48 lines used are removed from this list.
49 format_key -- '+' return first line in list with "add" markup around
50 the entire line.
51 '-' return first line in list with "delete" markup around
52 the entire line.
53 '?' return first line in list with add/delete/change
54 intraline markup (indices obtained from second line)
55 None return first line in list with no markup
56 side -- indice into the num_lines list (0=from,1=to)
57 num_lines -- from/to current line number. This is NOT intended to be a
58 passed parameter. It is present as a keyword argument to
59 maintain memory of the current line numbers between calls
60 of this function.
61
62 Note, this function is purposefully not defined at the module scope so
63 that data it needs from its parent function (within whose context it
64 is defined) does not need to be of module scope.
65 """
66 num_lines[side] += 1
67 # Handle case where no user markup is to be added, just return line of
68 # text with user's line format to allow for usage of the line number.
69 if format_key is None:
70 return (num_lines[side],lines.pop(0)[2:])
71 # Handle case of intraline changes
72 if format_key == '?':
73 text, markers = lines.pop(0), lines.pop(0)
74 # find intraline changes (store change type and indices in tuples)
75 sub_info = []
76 def record_sub_info(match_object,sub_info=sub_info):
77 sub_info.append([match_object.group(1)[0],match_object.span()])
78 return match_object.group(1)
79 change_re.sub(record_sub_info,markers)
80 # process each tuple inserting our special marks that won't be
81 # noticed by an xml/html escaper.
82 for key,(begin,end) in reversed(sub_info):
83 text = text[0:begin]+'\0'+key+text[begin:end]+'\1'+text[end:]
84 text = text[2:]
85 # Handle case of add/delete entire line
86 else:
87 text = lines.pop(0)[2:]
88 # if line of text is just a newline, insert a space so there is
89 # something for the user to highlight and see.
90 if not text:
91 text = ' '
92 # insert marks that won't be noticed by an xml/html escaper.
93 text = '\0' + format_key + text + '\1'
94 # Return line of text, first allow user's line formatter to do its
95 # thing (such as adding the line number) then replace the special
96 # marks with what the user's change markup.
97 return (num_lines[side],text)
98
99 def _line_iterator():
100 """Yields from/to lines of text with a change indication.
101
102 This function is an iterator. It itself pulls lines from a
103 differencing iterator, processes them and yields them. When it can
104 it yields both a "from" and a "to" line, otherwise it will yield one
105 or the other. In addition to yielding the lines of from/to text, a
106 boolean flag is yielded to indicate if the text line(s) have
107 differences in them.
108
109 Note, this function is purposefully not defined at the module scope so
110 that data it needs from its parent function (within whose context it
111 is defined) does not need to be of module scope.
112 """
113 lines = []
114 num_blanks_pending, num_blanks_to_yield = 0, 0
115 while True:
116 # Load up next 4 lines so we can look ahead, create strings which
117 # are a concatenation of the first character of each of the 4 lines
118 # so we can do some very readable comparisons.
119 while len(lines) < 4:
120 lines.append(next(diff_lines_iterator, 'X'))
121 s = ''.join([line[0] for line in lines])
122 if s.startswith('X'):
123 # When no more lines, pump out any remaining blank lines so the
124 # corresponding add/delete lines get a matching blank line so
125 # all line pairs get yielded at the next level.
126 num_blanks_to_yield = num_blanks_pending
127 elif s.startswith('-?+?'):
128 # simple intraline change
129 yield _make_line(lines,'?',0), _make_line(lines,'?',1), True
130 continue
131 elif s.startswith('--++'):
132 # in delete block, add block coming: we do NOT want to get
133 # caught up on blank lines yet, just process the delete line
134 num_blanks_pending -= 1
135 yield _make_line(lines,'-',0), None, True
136 continue
137 elif s.startswith(('--?+', '--+', '- ')):
138 # in delete block and see an intraline change or unchanged line
139 # coming: yield the delete line and then blanks
140 from_line,to_line = _make_line(lines,'-',0), None
141 num_blanks_to_yield,num_blanks_pending = num_blanks_pending-1,0
142 elif s.startswith('-+?'):
143 # intraline change
144 yield _make_line(lines,None,0), _make_line(lines,'?',1), True
145 continue
146 elif s.startswith('-?+'):
147 # intraline change
148 yield _make_line(lines,'?',0), _make_line(lines,None,1), True
149 continue
150 elif s.startswith('-'):
151 # delete FROM line
152 num_blanks_pending -= 1
153 yield _make_line(lines,'-',0), None, True
154 continue
155 elif s.startswith('+--'):
156 # in add block, delete block coming: we do NOT want to get
157 # caught up on blank lines yet, just process the add line
158 num_blanks_pending += 1
159 yield None, _make_line(lines,'+',1), True
160 continue
161 elif s.startswith(('+ ', '+-')):
162 # will be leaving an add block: yield blanks then add line
163 from_line, to_line = None, _make_line(lines,'+',1)
164 num_blanks_to_yield,num_blanks_pending = num_blanks_pending+1,0
165 elif s.startswith('+'):
166 # inside an add block, yield the add line
167 num_blanks_pending += 1
168 yield None, _make_line(lines,'+',1), True
169 continue
170 elif s.startswith(' '):
171 # unchanged text, yield it to both sides
172 yield _make_line(lines[:],None,0),_make_line(lines,None,1),False
173 continue
174 # Catch up on the blank lines so when we yield the next from/to
175 # pair, they are lined up.
176 while(num_blanks_to_yield < 0):
177 num_blanks_to_yield += 1
178 yield None,('','\n'),True
179 while(num_blanks_to_yield > 0):
180 num_blanks_to_yield -= 1
181 yield ('','\n'),None,True
182 if s.startswith('X'):
183 return
184 else:
185 yield from_line,to_line,True
186
187 def _line_pair_iterator():
188 """Yields from/to lines of text with a change indication.
189
190 This function is an iterator. It itself pulls lines from the line
191 iterator. Its difference from that iterator is that this function
192 always yields a pair of from/to text lines (with the change
193 indication). If necessary it will collect single from/to lines
194 until it has a matching pair from/to pair to yield.
195
196 Note, this function is purposefully not defined at the module scope so
197 that data it needs from its parent function (within whose context it
198 is defined) does not need to be of module scope.
199 """
200 line_iterator = _line_iterator()
201 fromlines,tolines=[],[]
202 while True:
203 # Collecting lines of text until we have a from/to pair
204 while (len(fromlines)==0 or len(tolines)==0):
205 try:
206 from_line, to_line, found_diff = next(line_iterator)
207 except StopIteration:
208 return
209 if from_line is not None:
210 fromlines.append((from_line,found_diff))
211 if to_line is not None:
212 tolines.append((to_line,found_diff))
213 # Once we have a pair, remove them from the collection and yield it
214 from_line, fromDiff = fromlines.pop(0)
215 to_line, to_diff = tolines.pop(0)
216 yield (from_line,to_line,fromDiff or to_diff)
217
218 # Handle case where user does not want context differencing, just yield
219 # them up without doing anything else with them.
220 line_pair_iterator = _line_pair_iterator()
221 if context is None:
222 yield from line_pair_iterator
223 # Handle case where user wants context differencing. We must do some
224 # storage of lines until we know for sure that they are to be yielded.
225 else:
226 context += 1
227 lines_to_write = 0
228 while True:
229 # Store lines up until we find a difference, note use of a
230 # circular queue because we only need to keep around what
231 # we need for context.
232 index, contextLines = 0, [None]*(context)
233 found_diff = False
234 while(found_diff is False):
235 try:
236 from_line, to_line, found_diff = next(line_pair_iterator)
237 except StopIteration:
238 return
239 i = index % context
240 contextLines[i] = (from_line, to_line, found_diff)
241 index += 1
242 # Yield lines that we have collected so far, but first yield
243 # the user's separator.
244 if index > context:
245 yield None, None, None
246 lines_to_write = context
247 else:
248 lines_to_write = index
249 index = 0
250 while(lines_to_write):
251 i = index % context
252 index += 1
253 yield contextLines[i]
254 lines_to_write -= 1
255 # Now yield the context lines after the change
256 lines_to_write = context-1
257 try:
258 while(lines_to_write):
259 from_line, to_line, found_diff = next(line_pair_iterator)
260 # If another change within the context, extend the context
261 if found_diff:
262 lines_to_write = context-1
263 else:
264 lines_to_write -= 1
265 yield from_line, to_line, found_diff
266 except StopIteration:
267 # Catch exception from next() and return normally
268 return
Path 2: 2 calls (0.11)
['', ' 1. Beautiful is beTTer than ugly.', ' 2. Explicit is better than implicit.', ' 3. Simple is better than complex.', ' 4. Complex is bett...
['', ' 1. Beautiful is better than ugly.', ' 3. Simple is better than complex.', ' 4. Complicated is better than complex.', ' 5. Flat is bet...
0 (2)
None (2)
IS_CHARACTER_JUNK def (2)
tuple (24) (None, None, None) (7)
None (2)
StopIteration (2)
1def _mdiff(fromlines, tolines, context=None, linejunk=None,
2 charjunk=IS_CHARACTER_JUNK):
3 r"""Returns generator yielding marked up from/to side by side differences.
4
5 Arguments:
6 fromlines -- list of text lines to compared to tolines
7 tolines -- list of text lines to be compared to fromlines
8 context -- number of context lines to display on each side of difference,
9 if None, all from/to text lines will be generated.
10 linejunk -- passed on to ndiff (see ndiff documentation)
11 charjunk -- passed on to ndiff (see ndiff documentation)
12
13 This function returns an iterator which returns a tuple:
14 (from line tuple, to line tuple, boolean flag)
15
16 from/to line tuple -- (line num, line text)
17 line num -- integer or None (to indicate a context separation)
18 line text -- original line text with following markers inserted:
19 '\0+' -- marks start of added text
20 '\0-' -- marks start of deleted text
21 '\0^' -- marks start of changed text
22 '\1' -- marks end of added/deleted/changed text
23
24 boolean flag -- None indicates context separation, True indicates
25 either "from" or "to" line contains a change, otherwise False.
26
27 This function/iterator was originally developed to generate side by side
28 file difference for making HTML pages (see HtmlDiff class for example
29 usage).
30
31 Note, this function utilizes the ndiff function to generate the side by
32 side difference markup. Optional ndiff arguments may be passed to this
33 function and they in turn will be passed to ndiff.
34 """
35 import re
36
37 # regular expression for finding intraline change indices
38 change_re = re.compile(r'(\++|\-+|\^+)')
39
40 # create the difference iterator to generate the differences
41 diff_lines_iterator = ndiff(fromlines,tolines,linejunk,charjunk)
42
43 def _make_line(lines, format_key, side, num_lines=[0,0]):
44 """Returns line of text with user's change markup and line formatting.
45
46 lines -- list of lines from the ndiff generator to produce a line of
47 text from. When producing the line of text to return, the
48 lines used are removed from this list.
49 format_key -- '+' return first line in list with "add" markup around
50 the entire line.
51 '-' return first line in list with "delete" markup around
52 the entire line.
53 '?' return first line in list with add/delete/change
54 intraline markup (indices obtained from second line)
55 None return first line in list with no markup
56 side -- indice into the num_lines list (0=from,1=to)
57 num_lines -- from/to current line number. This is NOT intended to be a
58 passed parameter. It is present as a keyword argument to
59 maintain memory of the current line numbers between calls
60 of this function.
61
62 Note, this function is purposefully not defined at the module scope so
63 that data it needs from its parent function (within whose context it
64 is defined) does not need to be of module scope.
65 """
66 num_lines[side] += 1
67 # Handle case where no user markup is to be added, just return line of
68 # text with user's line format to allow for usage of the line number.
69 if format_key is None:
70 return (num_lines[side],lines.pop(0)[2:])
71 # Handle case of intraline changes
72 if format_key == '?':
73 text, markers = lines.pop(0), lines.pop(0)
74 # find intraline changes (store change type and indices in tuples)
75 sub_info = []
76 def record_sub_info(match_object,sub_info=sub_info):
77 sub_info.append([match_object.group(1)[0],match_object.span()])
78 return match_object.group(1)
79 change_re.sub(record_sub_info,markers)
80 # process each tuple inserting our special marks that won't be
81 # noticed by an xml/html escaper.
82 for key,(begin,end) in reversed(sub_info):
83 text = text[0:begin]+'\0'+key+text[begin:end]+'\1'+text[end:]
84 text = text[2:]
85 # Handle case of add/delete entire line
86 else:
87 text = lines.pop(0)[2:]
88 # if line of text is just a newline, insert a space so there is
89 # something for the user to highlight and see.
90 if not text:
91 text = ' '
92 # insert marks that won't be noticed by an xml/html escaper.
93 text = '\0' + format_key + text + '\1'
94 # Return line of text, first allow user's line formatter to do its
95 # thing (such as adding the line number) then replace the special
96 # marks with what the user's change markup.
97 return (num_lines[side],text)
98
99 def _line_iterator():
100 """Yields from/to lines of text with a change indication.
101
102 This function is an iterator. It itself pulls lines from a
103 differencing iterator, processes them and yields them. When it can
104 it yields both a "from" and a "to" line, otherwise it will yield one
105 or the other. In addition to yielding the lines of from/to text, a
106 boolean flag is yielded to indicate if the text line(s) have
107 differences in them.
108
109 Note, this function is purposefully not defined at the module scope so
110 that data it needs from its parent function (within whose context it
111 is defined) does not need to be of module scope.
112 """
113 lines = []
114 num_blanks_pending, num_blanks_to_yield = 0, 0
115 while True:
116 # Load up next 4 lines so we can look ahead, create strings which
117 # are a concatenation of the first character of each of the 4 lines
118 # so we can do some very readable comparisons.
119 while len(lines) < 4:
120 lines.append(next(diff_lines_iterator, 'X'))
121 s = ''.join([line[0] for line in lines])
122 if s.startswith('X'):
123 # When no more lines, pump out any remaining blank lines so the
124 # corresponding add/delete lines get a matching blank line so
125 # all line pairs get yielded at the next level.
126 num_blanks_to_yield = num_blanks_pending
127 elif s.startswith('-?+?'):
128 # simple intraline change
129 yield _make_line(lines,'?',0), _make_line(lines,'?',1), True
130 continue
131 elif s.startswith('--++'):
132 # in delete block, add block coming: we do NOT want to get
133 # caught up on blank lines yet, just process the delete line
134 num_blanks_pending -= 1
135 yield _make_line(lines,'-',0), None, True
136 continue
137 elif s.startswith(('--?+', '--+', '- ')):
138 # in delete block and see an intraline change or unchanged line
139 # coming: yield the delete line and then blanks
140 from_line,to_line = _make_line(lines,'-',0), None
141 num_blanks_to_yield,num_blanks_pending = num_blanks_pending-1,0
142 elif s.startswith('-+?'):
143 # intraline change
144 yield _make_line(lines,None,0), _make_line(lines,'?',1), True
145 continue
146 elif s.startswith('-?+'):
147 # intraline change
148 yield _make_line(lines,'?',0), _make_line(lines,None,1), True
149 continue
150 elif s.startswith('-'):
151 # delete FROM line
152 num_blanks_pending -= 1
153 yield _make_line(lines,'-',0), None, True
154 continue
155 elif s.startswith('+--'):
156 # in add block, delete block coming: we do NOT want to get
157 # caught up on blank lines yet, just process the add line
158 num_blanks_pending += 1
159 yield None, _make_line(lines,'+',1), True
160 continue
161 elif s.startswith(('+ ', '+-')):
162 # will be leaving an add block: yield blanks then add line
163 from_line, to_line = None, _make_line(lines,'+',1)
164 num_blanks_to_yield,num_blanks_pending = num_blanks_pending+1,0
165 elif s.startswith('+'):
166 # inside an add block, yield the add line
167 num_blanks_pending += 1
168 yield None, _make_line(lines,'+',1), True
169 continue
170 elif s.startswith(' '):
171 # unchanged text, yield it to both sides
172 yield _make_line(lines[:],None,0),_make_line(lines,None,1),False
173 continue
174 # Catch up on the blank lines so when we yield the next from/to
175 # pair, they are lined up.
176 while(num_blanks_to_yield < 0):
177 num_blanks_to_yield += 1
178 yield None,('','\n'),True
179 while(num_blanks_to_yield > 0):
180 num_blanks_to_yield -= 1
181 yield ('','\n'),None,True
182 if s.startswith('X'):
183 return
184 else:
185 yield from_line,to_line,True
186
187 def _line_pair_iterator():
188 """Yields from/to lines of text with a change indication.
189
190 This function is an iterator. It itself pulls lines from the line
191 iterator. Its difference from that iterator is that this function
192 always yields a pair of from/to text lines (with the change
193 indication). If necessary it will collect single from/to lines
194 until it has a matching pair from/to pair to yield.
195
196 Note, this function is purposefully not defined at the module scope so
197 that data it needs from its parent function (within whose context it
198 is defined) does not need to be of module scope.
199 """
200 line_iterator = _line_iterator()
201 fromlines,tolines=[],[]
202 while True:
203 # Collecting lines of text until we have a from/to pair
204 while (len(fromlines)==0 or len(tolines)==0):
205 try:
206 from_line, to_line, found_diff = next(line_iterator)
207 except StopIteration:
208 return
209 if from_line is not None:
210 fromlines.append((from_line,found_diff))
211 if to_line is not None:
212 tolines.append((to_line,found_diff))
213 # Once we have a pair, remove them from the collection and yield it
214 from_line, fromDiff = fromlines.pop(0)
215 to_line, to_diff = tolines.pop(0)
216 yield (from_line,to_line,fromDiff or to_diff)
217
218 # Handle case where user does not want context differencing, just yield
219 # them up without doing anything else with them.
220 line_pair_iterator = _line_pair_iterator()
221 if context is None:
222 yield from line_pair_iterator
223 # Handle case where user wants context differencing. We must do some
224 # storage of lines until we know for sure that they are to be yielded.
225 else:
226 context += 1
227 lines_to_write = 0
228 while True:
229 # Store lines up until we find a difference, note use of a
230 # circular queue because we only need to keep around what
231 # we need for context.
232 index, contextLines = 0, [None]*(context)
233 found_diff = False
234 while(found_diff is False):
235 try:
236 from_line, to_line, found_diff = next(line_pair_iterator)
237 except StopIteration:
238 return
239 i = index % context
240 contextLines[i] = (from_line, to_line, found_diff)
241 index += 1
242 # Yield lines that we have collected so far, but first yield
243 # the user's separator.
244 if index > context:
245 yield None, None, None
246 lines_to_write = context
247 else:
248 lines_to_write = index
249 index = 0
250 while(lines_to_write):
251 i = index % context
252 index += 1
253 yield contextLines[i]
254 lines_to_write -= 1
255 # Now yield the context lines after the change
256 lines_to_write = context-1
257 try:
258 while(lines_to_write):
259 from_line, to_line, found_diff = next(line_pair_iterator)
260 # If another change within the context, extend the context
261 if found_diff:
262 lines_to_write = context-1
263 else:
264 lines_to_write -= 1
265 yield from_line, to_line, found_diff
266 except StopIteration:
267 # Catch exception from next() and return normally
268 return
Path 3: 1 calls (0.06)
['2'] (1)
['3'] (1)
1 (1)
None (1)
IS_CHARACTER_JUNK def (1)
tuple (1)
None (1)
StopIteration (1)
1def _mdiff(fromlines, tolines, context=None, linejunk=None,
2 charjunk=IS_CHARACTER_JUNK):
3 r"""Returns generator yielding marked up from/to side by side differences.
4
5 Arguments:
6 fromlines -- list of text lines to compared to tolines
7 tolines -- list of text lines to be compared to fromlines
8 context -- number of context lines to display on each side of difference,
9 if None, all from/to text lines will be generated.
10 linejunk -- passed on to ndiff (see ndiff documentation)
11 charjunk -- passed on to ndiff (see ndiff documentation)
12
13 This function returns an iterator which returns a tuple:
14 (from line tuple, to line tuple, boolean flag)
15
16 from/to line tuple -- (line num, line text)
17 line num -- integer or None (to indicate a context separation)
18 line text -- original line text with following markers inserted:
19 '\0+' -- marks start of added text
20 '\0-' -- marks start of deleted text
21 '\0^' -- marks start of changed text
22 '\1' -- marks end of added/deleted/changed text
23
24 boolean flag -- None indicates context separation, True indicates
25 either "from" or "to" line contains a change, otherwise False.
26
27 This function/iterator was originally developed to generate side by side
28 file difference for making HTML pages (see HtmlDiff class for example
29 usage).
30
31 Note, this function utilizes the ndiff function to generate the side by
32 side difference markup. Optional ndiff arguments may be passed to this
33 function and they in turn will be passed to ndiff.
34 """
35 import re
36
37 # regular expression for finding intraline change indices
38 change_re = re.compile(r'(\++|\-+|\^+)')
39
40 # create the difference iterator to generate the differences
41 diff_lines_iterator = ndiff(fromlines,tolines,linejunk,charjunk)
42
43 def _make_line(lines, format_key, side, num_lines=[0,0]):
44 """Returns line of text with user's change markup and line formatting.
45
46 lines -- list of lines from the ndiff generator to produce a line of
47 text from. When producing the line of text to return, the
48 lines used are removed from this list.
49 format_key -- '+' return first line in list with "add" markup around
50 the entire line.
51 '-' return first line in list with "delete" markup around
52 the entire line.
53 '?' return first line in list with add/delete/change
54 intraline markup (indices obtained from second line)
55 None return first line in list with no markup
56 side -- indice into the num_lines list (0=from,1=to)
57 num_lines -- from/to current line number. This is NOT intended to be a
58 passed parameter. It is present as a keyword argument to
59 maintain memory of the current line numbers between calls
60 of this function.
61
62 Note, this function is purposefully not defined at the module scope so
63 that data it needs from its parent function (within whose context it
64 is defined) does not need to be of module scope.
65 """
66 num_lines[side] += 1
67 # Handle case where no user markup is to be added, just return line of
68 # text with user's line format to allow for usage of the line number.
69 if format_key is None:
70 return (num_lines[side],lines.pop(0)[2:])
71 # Handle case of intraline changes
72 if format_key == '?':
73 text, markers = lines.pop(0), lines.pop(0)
74 # find intraline changes (store change type and indices in tuples)
75 sub_info = []
76 def record_sub_info(match_object,sub_info=sub_info):
77 sub_info.append([match_object.group(1)[0],match_object.span()])
78 return match_object.group(1)
79 change_re.sub(record_sub_info,markers)
80 # process each tuple inserting our special marks that won't be
81 # noticed by an xml/html escaper.
82 for key,(begin,end) in reversed(sub_info):
83 text = text[0:begin]+'\0'+key+text[begin:end]+'\1'+text[end:]
84 text = text[2:]
85 # Handle case of add/delete entire line
86 else:
87 text = lines.pop(0)[2:]
88 # if line of text is just a newline, insert a space so there is
89 # something for the user to highlight and see.
90 if not text:
91 text = ' '
92 # insert marks that won't be noticed by an xml/html escaper.
93 text = '\0' + format_key + text + '\1'
94 # Return line of text, first allow user's line formatter to do its
95 # thing (such as adding the line number) then replace the special
96 # marks with what the user's change markup.
97 return (num_lines[side],text)
98
99 def _line_iterator():
100 """Yields from/to lines of text with a change indication.
101
102 This function is an iterator. It itself pulls lines from a
103 differencing iterator, processes them and yields them. When it can
104 it yields both a "from" and a "to" line, otherwise it will yield one
105 or the other. In addition to yielding the lines of from/to text, a
106 boolean flag is yielded to indicate if the text line(s) have
107 differences in them.
108
109 Note, this function is purposefully not defined at the module scope so
110 that data it needs from its parent function (within whose context it
111 is defined) does not need to be of module scope.
112 """
113 lines = []
114 num_blanks_pending, num_blanks_to_yield = 0, 0
115 while True:
116 # Load up next 4 lines so we can look ahead, create strings which
117 # are a concatenation of the first character of each of the 4 lines
118 # so we can do some very readable comparisons.
119 while len(lines) < 4:
120 lines.append(next(diff_lines_iterator, 'X'))
121 s = ''.join([line[0] for line in lines])
122 if s.startswith('X'):
123 # When no more lines, pump out any remaining blank lines so the
124 # corresponding add/delete lines get a matching blank line so
125 # all line pairs get yielded at the next level.
126 num_blanks_to_yield = num_blanks_pending
127 elif s.startswith('-?+?'):
128 # simple intraline change
129 yield _make_line(lines,'?',0), _make_line(lines,'?',1), True
130 continue
131 elif s.startswith('--++'):
132 # in delete block, add block coming: we do NOT want to get
133 # caught up on blank lines yet, just process the delete line
134 num_blanks_pending -= 1
135 yield _make_line(lines,'-',0), None, True
136 continue
137 elif s.startswith(('--?+', '--+', '- ')):
138 # in delete block and see an intraline change or unchanged line
139 # coming: yield the delete line and then blanks
140 from_line,to_line = _make_line(lines,'-',0), None
141 num_blanks_to_yield,num_blanks_pending = num_blanks_pending-1,0
142 elif s.startswith('-+?'):
143 # intraline change
144 yield _make_line(lines,None,0), _make_line(lines,'?',1), True
145 continue
146 elif s.startswith('-?+'):
147 # intraline change
148 yield _make_line(lines,'?',0), _make_line(lines,None,1), True
149 continue
150 elif s.startswith('-'):
151 # delete FROM line
152 num_blanks_pending -= 1
153 yield _make_line(lines,'-',0), None, True
154 continue
155 elif s.startswith('+--'):
156 # in add block, delete block coming: we do NOT want to get
157 # caught up on blank lines yet, just process the add line
158 num_blanks_pending += 1
159 yield None, _make_line(lines,'+',1), True
160 continue
161 elif s.startswith(('+ ', '+-')):
162 # will be leaving an add block: yield blanks then add line
163 from_line, to_line = None, _make_line(lines,'+',1)
164 num_blanks_to_yield,num_blanks_pending = num_blanks_pending+1,0
165 elif s.startswith('+'):
166 # inside an add block, yield the add line
167 num_blanks_pending += 1
168 yield None, _make_line(lines,'+',1), True
169 continue
170 elif s.startswith(' '):
171 # unchanged text, yield it to both sides
172 yield _make_line(lines[:],None,0),_make_line(lines,None,1),False
173 continue
174 # Catch up on the blank lines so when we yield the next from/to
175 # pair, they are lined up.
176 while(num_blanks_to_yield < 0):
177 num_blanks_to_yield += 1
178 yield None,('','\n'),True
179 while(num_blanks_to_yield > 0):
180 num_blanks_to_yield -= 1
181 yield ('','\n'),None,True
182 if s.startswith('X'):
183 return
184 else:
185 yield from_line,to_line,True
186
187 def _line_pair_iterator():
188 """Yields from/to lines of text with a change indication.
189
190 This function is an iterator. It itself pulls lines from the line
191 iterator. Its difference from that iterator is that this function
192 always yields a pair of from/to text lines (with the change
193 indication). If necessary it will collect single from/to lines
194 until it has a matching pair from/to pair to yield.
195
196 Note, this function is purposefully not defined at the module scope so
197 that data it needs from its parent function (within whose context it
198 is defined) does not need to be of module scope.
199 """
200 line_iterator = _line_iterator()
201 fromlines,tolines=[],[]
202 while True:
203 # Collecting lines of text until we have a from/to pair
204 while (len(fromlines)==0 or len(tolines)==0):
205 try:
206 from_line, to_line, found_diff = next(line_iterator)
207 except StopIteration:
208 return
209 if from_line is not None:
210 fromlines.append((from_line,found_diff))
211 if to_line is not None:
212 tolines.append((to_line,found_diff))
213 # Once we have a pair, remove them from the collection and yield it
214 from_line, fromDiff = fromlines.pop(0)
215 to_line, to_diff = tolines.pop(0)
216 yield (from_line,to_line,fromDiff or to_diff)
217
218 # Handle case where user does not want context differencing, just yield
219 # them up without doing anything else with them.
220 line_pair_iterator = _line_pair_iterator()
221 if context is None:
222 yield from line_pair_iterator
223 # Handle case where user wants context differencing. We must do some
224 # storage of lines until we know for sure that they are to be yielded.
225 else:
226 context += 1
227 lines_to_write = 0
228 while True:
229 # Store lines up until we find a difference, note use of a
230 # circular queue because we only need to keep around what
231 # we need for context.
232 index, contextLines = 0, [None]*(context)
233 found_diff = False
234 while(found_diff is False):
235 try:
236 from_line, to_line, found_diff = next(line_pair_iterator)
237 except StopIteration:
238 return
239 i = index % context
240 contextLines[i] = (from_line, to_line, found_diff)
241 index += 1
242 # Yield lines that we have collected so far, but first yield
243 # the user's separator.
244 if index > context:
245 yield None, None, None
246 lines_to_write = context
247 else:
248 lines_to_write = index
249 index = 0
250 while(lines_to_write):
251 i = index % context
252 index += 1
253 yield contextLines[i]
254 lines_to_write -= 1
255 # Now yield the context lines after the change
256 lines_to_write = context-1
257 try:
258 while(lines_to_write):
259 from_line, to_line, found_diff = next(line_pair_iterator)
260 # If another change within the context, extend the context
261 if found_diff:
262 lines_to_write = context-1
263 else:
264 lines_to_write -= 1
265 yield from_line, to_line, found_diff
266 except StopIteration:
267 # Catch exception from next() and return normally
268 return
Path 4: 1 calls (0.06)
['', ' 1. Beautiful is beTTer than ugly.', ' 2. Explicit is better than implicit.', ' 3. Simple is better than complex.', ' 4. Complex is bett...
['', ' 1. Beautiful is better than ugly.', ' 3. Simple is better than complex.', ' 4. Complicated is better than complex.', ' 5. Flat is bet...
5 (1)
None (1)
IS_CHARACTER_JUNK def (1)
tuple (41) (None, None, None) (2)
None (1)
StopIteration (1)
1def _mdiff(fromlines, tolines, context=None, linejunk=None,
2 charjunk=IS_CHARACTER_JUNK):
3 r"""Returns generator yielding marked up from/to side by side differences.
4
5 Arguments:
6 fromlines -- list of text lines to compared to tolines
7 tolines -- list of text lines to be compared to fromlines
8 context -- number of context lines to display on each side of difference,
9 if None, all from/to text lines will be generated.
10 linejunk -- passed on to ndiff (see ndiff documentation)
11 charjunk -- passed on to ndiff (see ndiff documentation)
12
13 This function returns an iterator which returns a tuple:
14 (from line tuple, to line tuple, boolean flag)
15
16 from/to line tuple -- (line num, line text)
17 line num -- integer or None (to indicate a context separation)
18 line text -- original line text with following markers inserted:
19 '\0+' -- marks start of added text
20 '\0-' -- marks start of deleted text
21 '\0^' -- marks start of changed text
22 '\1' -- marks end of added/deleted/changed text
23
24 boolean flag -- None indicates context separation, True indicates
25 either "from" or "to" line contains a change, otherwise False.
26
27 This function/iterator was originally developed to generate side by side
28 file difference for making HTML pages (see HtmlDiff class for example
29 usage).
30
31 Note, this function utilizes the ndiff function to generate the side by
32 side difference markup. Optional ndiff arguments may be passed to this
33 function and they in turn will be passed to ndiff.
34 """
35 import re
36
37 # regular expression for finding intraline change indices
38 change_re = re.compile(r'(\++|\-+|\^+)')
39
40 # create the difference iterator to generate the differences
41 diff_lines_iterator = ndiff(fromlines,tolines,linejunk,charjunk)
42
43 def _make_line(lines, format_key, side, num_lines=[0,0]):
44 """Returns line of text with user's change markup and line formatting.
45
46 lines -- list of lines from the ndiff generator to produce a line of
47 text from. When producing the line of text to return, the
48 lines used are removed from this list.
49 format_key -- '+' return first line in list with "add" markup around
50 the entire line.
51 '-' return first line in list with "delete" markup around
52 the entire line.
53 '?' return first line in list with add/delete/change
54 intraline markup (indices obtained from second line)
55 None return first line in list with no markup
56 side -- indice into the num_lines list (0=from,1=to)
57 num_lines -- from/to current line number. This is NOT intended to be a
58 passed parameter. It is present as a keyword argument to
59 maintain memory of the current line numbers between calls
60 of this function.
61
62 Note, this function is purposefully not defined at the module scope so
63 that data it needs from its parent function (within whose context it
64 is defined) does not need to be of module scope.
65 """
66 num_lines[side] += 1
67 # Handle case where no user markup is to be added, just return line of
68 # text with user's line format to allow for usage of the line number.
69 if format_key is None:
70 return (num_lines[side],lines.pop(0)[2:])
71 # Handle case of intraline changes
72 if format_key == '?':
73 text, markers = lines.pop(0), lines.pop(0)
74 # find intraline changes (store change type and indices in tuples)
75 sub_info = []
76 def record_sub_info(match_object,sub_info=sub_info):
77 sub_info.append([match_object.group(1)[0],match_object.span()])
78 return match_object.group(1)
79 change_re.sub(record_sub_info,markers)
80 # process each tuple inserting our special marks that won't be
81 # noticed by an xml/html escaper.
82 for key,(begin,end) in reversed(sub_info):
83 text = text[0:begin]+'\0'+key+text[begin:end]+'\1'+text[end:]
84 text = text[2:]
85 # Handle case of add/delete entire line
86 else:
87 text = lines.pop(0)[2:]
88 # if line of text is just a newline, insert a space so there is
89 # something for the user to highlight and see.
90 if not text:
91 text = ' '
92 # insert marks that won't be noticed by an xml/html escaper.
93 text = '\0' + format_key + text + '\1'
94 # Return line of text, first allow user's line formatter to do its
95 # thing (such as adding the line number) then replace the special
96 # marks with what the user's change markup.
97 return (num_lines[side],text)
98
99 def _line_iterator():
100 """Yields from/to lines of text with a change indication.
101
102 This function is an iterator. It itself pulls lines from a
103 differencing iterator, processes them and yields them. When it can
104 it yields both a "from" and a "to" line, otherwise it will yield one
105 or the other. In addition to yielding the lines of from/to text, a
106 boolean flag is yielded to indicate if the text line(s) have
107 differences in them.
108
109 Note, this function is purposefully not defined at the module scope so
110 that data it needs from its parent function (within whose context it
111 is defined) does not need to be of module scope.
112 """
113 lines = []
114 num_blanks_pending, num_blanks_to_yield = 0, 0
115 while True:
116 # Load up next 4 lines so we can look ahead, create strings which
117 # are a concatenation of the first character of each of the 4 lines
118 # so we can do some very readable comparisons.
119 while len(lines) < 4:
120 lines.append(next(diff_lines_iterator, 'X'))
121 s = ''.join([line[0] for line in lines])
122 if s.startswith('X'):
123 # When no more lines, pump out any remaining blank lines so the
124 # corresponding add/delete lines get a matching blank line so
125 # all line pairs get yielded at the next level.
126 num_blanks_to_yield = num_blanks_pending
127 elif s.startswith('-?+?'):
128 # simple intraline change
129 yield _make_line(lines,'?',0), _make_line(lines,'?',1), True
130 continue
131 elif s.startswith('--++'):
132 # in delete block, add block coming: we do NOT want to get
133 # caught up on blank lines yet, just process the delete line
134 num_blanks_pending -= 1
135 yield _make_line(lines,'-',0), None, True
136 continue
137 elif s.startswith(('--?+', '--+', '- ')):
138 # in delete block and see an intraline change or unchanged line
139 # coming: yield the delete line and then blanks
140 from_line,to_line = _make_line(lines,'-',0), None
141 num_blanks_to_yield,num_blanks_pending = num_blanks_pending-1,0
142 elif s.startswith('-+?'):
143 # intraline change
144 yield _make_line(lines,None,0), _make_line(lines,'?',1), True
145 continue
146 elif s.startswith('-?+'):
147 # intraline change
148 yield _make_line(lines,'?',0), _make_line(lines,None,1), True
149 continue
150 elif s.startswith('-'):
151 # delete FROM line
152 num_blanks_pending -= 1
153 yield _make_line(lines,'-',0), None, True
154 continue
155 elif s.startswith('+--'):
156 # in add block, delete block coming: we do NOT want to get
157 # caught up on blank lines yet, just process the add line
158 num_blanks_pending += 1
159 yield None, _make_line(lines,'+',1), True
160 continue
161 elif s.startswith(('+ ', '+-')):
162 # will be leaving an add block: yield blanks then add line
163 from_line, to_line = None, _make_line(lines,'+',1)
164 num_blanks_to_yield,num_blanks_pending = num_blanks_pending+1,0
165 elif s.startswith('+'):
166 # inside an add block, yield the add line
167 num_blanks_pending += 1
168 yield None, _make_line(lines,'+',1), True
169 continue
170 elif s.startswith(' '):
171 # unchanged text, yield it to both sides
172 yield _make_line(lines[:],None,0),_make_line(lines,None,1),False
173 continue
174 # Catch up on the blank lines so when we yield the next from/to
175 # pair, they are lined up.
176 while(num_blanks_to_yield < 0):
177 num_blanks_to_yield += 1
178 yield None,('','\n'),True
179 while(num_blanks_to_yield > 0):
180 num_blanks_to_yield -= 1
181 yield ('','\n'),None,True
182 if s.startswith('X'):
183 return
184 else:
185 yield from_line,to_line,True
186
187 def _line_pair_iterator():
188 """Yields from/to lines of text with a change indication.
189
190 This function is an iterator. It itself pulls lines from the line
191 iterator. Its difference from that iterator is that this function
192 always yields a pair of from/to text lines (with the change
193 indication). If necessary it will collect single from/to lines
194 until it has a matching pair from/to pair to yield.
195
196 Note, this function is purposefully not defined at the module scope so
197 that data it needs from its parent function (within whose context it
198 is defined) does not need to be of module scope.
199 """
200 line_iterator = _line_iterator()
201 fromlines,tolines=[],[]
202 while True:
203 # Collecting lines of text until we have a from/to pair
204 while (len(fromlines)==0 or len(tolines)==0):
205 try:
206 from_line, to_line, found_diff = next(line_iterator)
207 except StopIteration:
208 return
209 if from_line is not None:
210 fromlines.append((from_line,found_diff))
211 if to_line is not None:
212 tolines.append((to_line,found_diff))
213 # Once we have a pair, remove them from the collection and yield it
214 from_line, fromDiff = fromlines.pop(0)
215 to_line, to_diff = tolines.pop(0)
216 yield (from_line,to_line,fromDiff or to_diff)
217
218 # Handle case where user does not want context differencing, just yield
219 # them up without doing anything else with them.
220 line_pair_iterator = _line_pair_iterator()
221 if context is None:
222 yield from line_pair_iterator
223 # Handle case where user wants context differencing. We must do some
224 # storage of lines until we know for sure that they are to be yielded.
225 else:
226 context += 1
227 lines_to_write = 0
228 while True:
229 # Store lines up until we find a difference, note use of a
230 # circular queue because we only need to keep around what
231 # we need for context.
232 index, contextLines = 0, [None]*(context)
233 found_diff = False
234 while(found_diff is False):
235 try:
236 from_line, to_line, found_diff = next(line_pair_iterator)
237 except StopIteration:
238 return
239 i = index % context
240 contextLines[i] = (from_line, to_line, found_diff)
241 index += 1
242 # Yield lines that we have collected so far, but first yield
243 # the user's separator.
244 if index > context:
245 yield None, None, None
246 lines_to_write = context
247 else:
248 lines_to_write = index
249 index = 0
250 while(lines_to_write):
251 i = index % context
252 index += 1
253 yield contextLines[i]
254 lines_to_write -= 1
255 # Now yield the context lines after the change
256 lines_to_write = context-1
257 try:
258 while(lines_to_write):
259 from_line, to_line, found_diff = next(line_pair_iterator)
260 # If another change within the context, extend the context
261 if found_diff:
262 lines_to_write = context-1
263 else:
264 lines_to_write -= 1
265 yield from_line, to_line, found_diff
266 except StopIteration:
267 # Catch exception from next() and return normally
268 return
Path 5: 1 calls (0.06)
['456', '456', '456', '456', '456', '456', '456', '456', '456', '456', '', ' 1. Beautiful is beTTer than ugly.', ' 2. Explicit is better than impl...
['456', '456', '456', '456', '456', '456', '456', '456', '456', '456', '', ' 1. Beautiful is better than ugly.', ' 3. Simple is better than comp...
5 (1)
None (1)
IS_CHARACTER_JUNK def (1)
tuple (45) (None, None, None) (3)
None (1)
StopIteration (1)
1def _mdiff(fromlines, tolines, context=None, linejunk=None,
2 charjunk=IS_CHARACTER_JUNK):
3 r"""Returns generator yielding marked up from/to side by side differences.
4
5 Arguments:
6 fromlines -- list of text lines to compared to tolines
7 tolines -- list of text lines to be compared to fromlines
8 context -- number of context lines to display on each side of difference,
9 if None, all from/to text lines will be generated.
10 linejunk -- passed on to ndiff (see ndiff documentation)
11 charjunk -- passed on to ndiff (see ndiff documentation)
12
13 This function returns an iterator which returns a tuple:
14 (from line tuple, to line tuple, boolean flag)
15
16 from/to line tuple -- (line num, line text)
17 line num -- integer or None (to indicate a context separation)
18 line text -- original line text with following markers inserted:
19 '\0+' -- marks start of added text
20 '\0-' -- marks start of deleted text
21 '\0^' -- marks start of changed text
22 '\1' -- marks end of added/deleted/changed text
23
24 boolean flag -- None indicates context separation, True indicates
25 either "from" or "to" line contains a change, otherwise False.
26
27 This function/iterator was originally developed to generate side by side
28 file difference for making HTML pages (see HtmlDiff class for example
29 usage).
30
31 Note, this function utilizes the ndiff function to generate the side by
32 side difference markup. Optional ndiff arguments may be passed to this
33 function and they in turn will be passed to ndiff.
34 """
35 import re
36
37 # regular expression for finding intraline change indices
38 change_re = re.compile(r'(\++|\-+|\^+)')
39
40 # create the difference iterator to generate the differences
41 diff_lines_iterator = ndiff(fromlines,tolines,linejunk,charjunk)
42
43 def _make_line(lines, format_key, side, num_lines=[0,0]):
44 """Returns line of text with user's change markup and line formatting.
45
46 lines -- list of lines from the ndiff generator to produce a line of
47 text from. When producing the line of text to return, the
48 lines used are removed from this list.
49 format_key -- '+' return first line in list with "add" markup around
50 the entire line.
51 '-' return first line in list with "delete" markup around
52 the entire line.
53 '?' return first line in list with add/delete/change
54 intraline markup (indices obtained from second line)
55 None return first line in list with no markup
56 side -- indice into the num_lines list (0=from,1=to)
57 num_lines -- from/to current line number. This is NOT intended to be a
58 passed parameter. It is present as a keyword argument to
59 maintain memory of the current line numbers between calls
60 of this function.
61
62 Note, this function is purposefully not defined at the module scope so
63 that data it needs from its parent function (within whose context it
64 is defined) does not need to be of module scope.
65 """
66 num_lines[side] += 1
67 # Handle case where no user markup is to be added, just return line of
68 # text with user's line format to allow for usage of the line number.
69 if format_key is None:
70 return (num_lines[side],lines.pop(0)[2:])
71 # Handle case of intraline changes
72 if format_key == '?':
73 text, markers = lines.pop(0), lines.pop(0)
74 # find intraline changes (store change type and indices in tuples)
75 sub_info = []
76 def record_sub_info(match_object,sub_info=sub_info):
77 sub_info.append([match_object.group(1)[0],match_object.span()])
78 return match_object.group(1)
79 change_re.sub(record_sub_info,markers)
80 # process each tuple inserting our special marks that won't be
81 # noticed by an xml/html escaper.
82 for key,(begin,end) in reversed(sub_info):
83 text = text[0:begin]+'\0'+key+text[begin:end]+'\1'+text[end:]
84 text = text[2:]
85 # Handle case of add/delete entire line
86 else:
87 text = lines.pop(0)[2:]
88 # if line of text is just a newline, insert a space so there is
89 # something for the user to highlight and see.
90 if not text:
91 text = ' '
92 # insert marks that won't be noticed by an xml/html escaper.
93 text = '\0' + format_key + text + '\1'
94 # Return line of text, first allow user's line formatter to do its
95 # thing (such as adding the line number) then replace the special
96 # marks with what the user's change markup.
97 return (num_lines[side],text)
98
99 def _line_iterator():
100 """Yields from/to lines of text with a change indication.
101
102 This function is an iterator. It itself pulls lines from a
103 differencing iterator, processes them and yields them. When it can
104 it yields both a "from" and a "to" line, otherwise it will yield one
105 or the other. In addition to yielding the lines of from/to text, a
106 boolean flag is yielded to indicate if the text line(s) have
107 differences in them.
108
109 Note, this function is purposefully not defined at the module scope so
110 that data it needs from its parent function (within whose context it
111 is defined) does not need to be of module scope.
112 """
113 lines = []
114 num_blanks_pending, num_blanks_to_yield = 0, 0
115 while True:
116 # Load up next 4 lines so we can look ahead, create strings which
117 # are a concatenation of the first character of each of the 4 lines
118 # so we can do some very readable comparisons.
119 while len(lines) < 4:
120 lines.append(next(diff_lines_iterator, 'X'))
121 s = ''.join([line[0] for line in lines])
122 if s.startswith('X'):
123 # When no more lines, pump out any remaining blank lines so the
124 # corresponding add/delete lines get a matching blank line so
125 # all line pairs get yielded at the next level.
126 num_blanks_to_yield = num_blanks_pending
127 elif s.startswith('-?+?'):
128 # simple intraline change
129 yield _make_line(lines,'?',0), _make_line(lines,'?',1), True
130 continue
131 elif s.startswith('--++'):
132 # in delete block, add block coming: we do NOT want to get
133 # caught up on blank lines yet, just process the delete line
134 num_blanks_pending -= 1
135 yield _make_line(lines,'-',0), None, True
136 continue
137 elif s.startswith(('--?+', '--+', '- ')):
138 # in delete block and see an intraline change or unchanged line
139 # coming: yield the delete line and then blanks
140 from_line,to_line = _make_line(lines,'-',0), None
141 num_blanks_to_yield,num_blanks_pending = num_blanks_pending-1,0
142 elif s.startswith('-+?'):
143 # intraline change
144 yield _make_line(lines,None,0), _make_line(lines,'?',1), True
145 continue
146 elif s.startswith('-?+'):
147 # intraline change
148 yield _make_line(lines,'?',0), _make_line(lines,None,1), True
149 continue
150 elif s.startswith('-'):
151 # delete FROM line
152 num_blanks_pending -= 1
153 yield _make_line(lines,'-',0), None, True
154 continue
155 elif s.startswith('+--'):
156 # in add block, delete block coming: we do NOT want to get
157 # caught up on blank lines yet, just process the add line
158 num_blanks_pending += 1
159 yield None, _make_line(lines,'+',1), True
160 continue
161 elif s.startswith(('+ ', '+-')):
162 # will be leaving an add block: yield blanks then add line
163 from_line, to_line = None, _make_line(lines,'+',1)
164 num_blanks_to_yield,num_blanks_pending = num_blanks_pending+1,0
165 elif s.startswith('+'):
166 # inside an add block, yield the add line
167 num_blanks_pending += 1
168 yield None, _make_line(lines,'+',1), True
169 continue
170 elif s.startswith(' '):
171 # unchanged text, yield it to both sides
172 yield _make_line(lines[:],None,0),_make_line(lines,None,1),False
173 continue
174 # Catch up on the blank lines so when we yield the next from/to
175 # pair, they are lined up.
176 while(num_blanks_to_yield < 0):
177 num_blanks_to_yield += 1
178 yield None,('','\n'),True
179 while(num_blanks_to_yield > 0):
180 num_blanks_to_yield -= 1
181 yield ('','\n'),None,True
182 if s.startswith('X'):
183 return
184 else:
185 yield from_line,to_line,True
186
187 def _line_pair_iterator():
188 """Yields from/to lines of text with a change indication.
189
190 This function is an iterator. It itself pulls lines from the line
191 iterator. Its difference from that iterator is that this function
192 always yields a pair of from/to text lines (with the change
193 indication). If necessary it will collect single from/to lines
194 until it has a matching pair from/to pair to yield.
195
196 Note, this function is purposefully not defined at the module scope so
197 that data it needs from its parent function (within whose context it
198 is defined) does not need to be of module scope.
199 """
200 line_iterator = _line_iterator()
201 fromlines,tolines=[],[]
202 while True:
203 # Collecting lines of text until we have a from/to pair
204 while (len(fromlines)==0 or len(tolines)==0):
205 try:
206 from_line, to_line, found_diff = next(line_iterator)
207 except StopIteration:
208 return
209 if from_line is not None:
210 fromlines.append((from_line,found_diff))
211 if to_line is not None:
212 tolines.append((to_line,found_diff))
213 # Once we have a pair, remove them from the collection and yield it
214 from_line, fromDiff = fromlines.pop(0)
215 to_line, to_diff = tolines.pop(0)
216 yield (from_line,to_line,fromDiff or to_diff)
217
218 # Handle case where user does not want context differencing, just yield
219 # them up without doing anything else with them.
220 line_pair_iterator = _line_pair_iterator()
221 if context is None:
222 yield from line_pair_iterator
223 # Handle case where user wants context differencing. We must do some
224 # storage of lines until we know for sure that they are to be yielded.
225 else:
226 context += 1
227 lines_to_write = 0
228 while True:
229 # Store lines up until we find a difference, note use of a
230 # circular queue because we only need to keep around what
231 # we need for context.
232 index, contextLines = 0, [None]*(context)
233 found_diff = False
234 while(found_diff is False):
235 try:
236 from_line, to_line, found_diff = next(line_pair_iterator)
237 except StopIteration:
238 return
239 i = index % context
240 contextLines[i] = (from_line, to_line, found_diff)
241 index += 1
242 # Yield lines that we have collected so far, but first yield
243 # the user's separator.
244 if index > context:
245 yield None, None, None
246 lines_to_write = context
247 else:
248 lines_to_write = index
249 index = 0
250 while(lines_to_write):
251 i = index % context
252 index += 1
253 yield contextLines[i]
254 lines_to_write -= 1
255 # Now yield the context lines after the change
256 lines_to_write = context-1
257 try:
258 while(lines_to_write):
259 from_line, to_line, found_diff = next(line_pair_iterator)
260 # If another change within the context, extend the context
261 if found_diff:
262 lines_to_write = context-1
263 else:
264 lines_to_write -= 1
265 yield from_line, to_line, found_diff
266 except StopIteration:
267 # Catch exception from next() and return normally
268 return
Path 6: 1 calls (0.06)
['', ' 1. Beautiful is beTTer than ugly.', ' 2. Explicit is better than implicit.', ' 3. Simple is better than complex.', ' 4. Complex is bett...
['', ' 1. Beautiful is better than ugly.', ' 3. Simple is better than complex.', ' 4. Complicated is better than complex.', ' 5. Flat is bet...
6 (1)
None (1)
IS_CHARACTER_JUNK def (1)
tuple (44)
None (1)
StopIteration (1)
1def _mdiff(fromlines, tolines, context=None, linejunk=None,
2 charjunk=IS_CHARACTER_JUNK):
3 r"""Returns generator yielding marked up from/to side by side differences.
4
5 Arguments:
6 fromlines -- list of text lines to compared to tolines
7 tolines -- list of text lines to be compared to fromlines
8 context -- number of context lines to display on each side of difference,
9 if None, all from/to text lines will be generated.
10 linejunk -- passed on to ndiff (see ndiff documentation)
11 charjunk -- passed on to ndiff (see ndiff documentation)
12
13 This function returns an iterator which returns a tuple:
14 (from line tuple, to line tuple, boolean flag)
15
16 from/to line tuple -- (line num, line text)
17 line num -- integer or None (to indicate a context separation)
18 line text -- original line text with following markers inserted:
19 '\0+' -- marks start of added text
20 '\0-' -- marks start of deleted text
21 '\0^' -- marks start of changed text
22 '\1' -- marks end of added/deleted/changed text
23
24 boolean flag -- None indicates context separation, True indicates
25 either "from" or "to" line contains a change, otherwise False.
26
27 This function/iterator was originally developed to generate side by side
28 file difference for making HTML pages (see HtmlDiff class for example
29 usage).
30
31 Note, this function utilizes the ndiff function to generate the side by
32 side difference markup. Optional ndiff arguments may be passed to this
33 function and they in turn will be passed to ndiff.
34 """
35 import re
36
37 # regular expression for finding intraline change indices
38 change_re = re.compile(r'(\++|\-+|\^+)')
39
40 # create the difference iterator to generate the differences
41 diff_lines_iterator = ndiff(fromlines,tolines,linejunk,charjunk)
42
43 def _make_line(lines, format_key, side, num_lines=[0,0]):
44 """Returns line of text with user's change markup and line formatting.
45
46 lines -- list of lines from the ndiff generator to produce a line of
47 text from. When producing the line of text to return, the
48 lines used are removed from this list.
49 format_key -- '+' return first line in list with "add" markup around
50 the entire line.
51 '-' return first line in list with "delete" markup around
52 the entire line.
53 '?' return first line in list with add/delete/change
54 intraline markup (indices obtained from second line)
55 None return first line in list with no markup
56 side -- indice into the num_lines list (0=from,1=to)
57 num_lines -- from/to current line number. This is NOT intended to be a
58 passed parameter. It is present as a keyword argument to
59 maintain memory of the current line numbers between calls
60 of this function.
61
62 Note, this function is purposefully not defined at the module scope so
63 that data it needs from its parent function (within whose context it
64 is defined) does not need to be of module scope.
65 """
66 num_lines[side] += 1
67 # Handle case where no user markup is to be added, just return line of
68 # text with user's line format to allow for usage of the line number.
69 if format_key is None:
70 return (num_lines[side],lines.pop(0)[2:])
71 # Handle case of intraline changes
72 if format_key == '?':
73 text, markers = lines.pop(0), lines.pop(0)
74 # find intraline changes (store change type and indices in tuples)
75 sub_info = []
76 def record_sub_info(match_object,sub_info=sub_info):
77 sub_info.append([match_object.group(1)[0],match_object.span()])
78 return match_object.group(1)
79 change_re.sub(record_sub_info,markers)
80 # process each tuple inserting our special marks that won't be
81 # noticed by an xml/html escaper.
82 for key,(begin,end) in reversed(sub_info):
83 text = text[0:begin]+'\0'+key+text[begin:end]+'\1'+text[end:]
84 text = text[2:]
85 # Handle case of add/delete entire line
86 else:
87 text = lines.pop(0)[2:]
88 # if line of text is just a newline, insert a space so there is
89 # something for the user to highlight and see.
90 if not text:
91 text = ' '
92 # insert marks that won't be noticed by an xml/html escaper.
93 text = '\0' + format_key + text + '\1'
94 # Return line of text, first allow user's line formatter to do its
95 # thing (such as adding the line number) then replace the special
96 # marks with what the user's change markup.
97 return (num_lines[side],text)
98
99 def _line_iterator():
100 """Yields from/to lines of text with a change indication.
101
102 This function is an iterator. It itself pulls lines from a
103 differencing iterator, processes them and yields them. When it can
104 it yields both a "from" and a "to" line, otherwise it will yield one
105 or the other. In addition to yielding the lines of from/to text, a
106 boolean flag is yielded to indicate if the text line(s) have
107 differences in them.
108
109 Note, this function is purposefully not defined at the module scope so
110 that data it needs from its parent function (within whose context it
111 is defined) does not need to be of module scope.
112 """
113 lines = []
114 num_blanks_pending, num_blanks_to_yield = 0, 0
115 while True:
116 # Load up next 4 lines so we can look ahead, create strings which
117 # are a concatenation of the first character of each of the 4 lines
118 # so we can do some very readable comparisons.
119 while len(lines) < 4:
120 lines.append(next(diff_lines_iterator, 'X'))
121 s = ''.join([line[0] for line in lines])
122 if s.startswith('X'):
123 # When no more lines, pump out any remaining blank lines so the
124 # corresponding add/delete lines get a matching blank line so
125 # all line pairs get yielded at the next level.
126 num_blanks_to_yield = num_blanks_pending
127 elif s.startswith('-?+?'):
128 # simple intraline change
129 yield _make_line(lines,'?',0), _make_line(lines,'?',1), True
130 continue
131 elif s.startswith('--++'):
132 # in delete block, add block coming: we do NOT want to get
133 # caught up on blank lines yet, just process the delete line
134 num_blanks_pending -= 1
135 yield _make_line(lines,'-',0), None, True
136 continue
137 elif s.startswith(('--?+', '--+', '- ')):
138 # in delete block and see an intraline change or unchanged line
139 # coming: yield the delete line and then blanks
140 from_line,to_line = _make_line(lines,'-',0), None
141 num_blanks_to_yield,num_blanks_pending = num_blanks_pending-1,0
142 elif s.startswith('-+?'):
143 # intraline change
144 yield _make_line(lines,None,0), _make_line(lines,'?',1), True
145 continue
146 elif s.startswith('-?+'):
147 # intraline change
148 yield _make_line(lines,'?',0), _make_line(lines,None,1), True
149 continue
150 elif s.startswith('-'):
151 # delete FROM line
152 num_blanks_pending -= 1
153 yield _make_line(lines,'-',0), None, True
154 continue
155 elif s.startswith('+--'):
156 # in add block, delete block coming: we do NOT want to get
157 # caught up on blank lines yet, just process the add line
158 num_blanks_pending += 1
159 yield None, _make_line(lines,'+',1), True
160 continue
161 elif s.startswith(('+ ', '+-')):
162 # will be leaving an add block: yield blanks then add line
163 from_line, to_line = None, _make_line(lines,'+',1)
164 num_blanks_to_yield,num_blanks_pending = num_blanks_pending+1,0
165 elif s.startswith('+'):
166 # inside an add block, yield the add line
167 num_blanks_pending += 1
168 yield None, _make_line(lines,'+',1), True
169 continue
170 elif s.startswith(' '):
171 # unchanged text, yield it to both sides
172 yield _make_line(lines[:],None,0),_make_line(lines,None,1),False
173 continue
174 # Catch up on the blank lines so when we yield the next from/to
175 # pair, they are lined up.
176 while(num_blanks_to_yield < 0):
177 num_blanks_to_yield += 1
178 yield None,('','\n'),True
179 while(num_blanks_to_yield > 0):
180 num_blanks_to_yield -= 1
181 yield ('','\n'),None,True
182 if s.startswith('X'):
183 return
184 else:
185 yield from_line,to_line,True
186
187 def _line_pair_iterator():
188 """Yields from/to lines of text with a change indication.
189
190 This function is an iterator. It itself pulls lines from the line
191 iterator. Its difference from that iterator is that this function
192 always yields a pair of from/to text lines (with the change
193 indication). If necessary it will collect single from/to lines
194 until it has a matching pair from/to pair to yield.
195
196 Note, this function is purposefully not defined at the module scope so
197 that data it needs from its parent function (within whose context it
198 is defined) does not need to be of module scope.
199 """
200 line_iterator = _line_iterator()
201 fromlines,tolines=[],[]
202 while True:
203 # Collecting lines of text until we have a from/to pair
204 while (len(fromlines)==0 or len(tolines)==0):
205 try:
206 from_line, to_line, found_diff = next(line_iterator)
207 except StopIteration:
208 return
209 if from_line is not None:
210 fromlines.append((from_line,found_diff))
211 if to_line is not None:
212 tolines.append((to_line,found_diff))
213 # Once we have a pair, remove them from the collection and yield it
214 from_line, fromDiff = fromlines.pop(0)
215 to_line, to_diff = tolines.pop(0)
216 yield (from_line,to_line,fromDiff or to_diff)
217
218 # Handle case where user does not want context differencing, just yield
219 # them up without doing anything else with them.
220 line_pair_iterator = _line_pair_iterator()
221 if context is None:
222 yield from line_pair_iterator
223 # Handle case where user wants context differencing. We must do some
224 # storage of lines until we know for sure that they are to be yielded.
225 else:
226 context += 1
227 lines_to_write = 0
228 while True:
229 # Store lines up until we find a difference, note use of a
230 # circular queue because we only need to keep around what
231 # we need for context.
232 index, contextLines = 0, [None]*(context)
233 found_diff = False
234 while(found_diff is False):
235 try:
236 from_line, to_line, found_diff = next(line_pair_iterator)
237 except StopIteration:
238 return
239 i = index % context
240 contextLines[i] = (from_line, to_line, found_diff)
241 index += 1
242 # Yield lines that we have collected so far, but first yield
243 # the user's separator.
244 if index > context:
245 yield None, None, None
246 lines_to_write = context
247 else:
248 lines_to_write = index
249 index = 0
250 while(lines_to_write):
251 i = index % context
252 index += 1
253 yield contextLines[i]
254 lines_to_write -= 1
255 # Now yield the context lines after the change
256 lines_to_write = context-1
257 try:
258 while(lines_to_write):
259 from_line, to_line, found_diff = next(line_pair_iterator)
260 # If another change within the context, extend the context
261 if found_diff:
262 lines_to_write = context-1
263 else:
264 lines_to_write -= 1
265 yield from_line, to_line, found_diff
266 except StopIteration:
267 # Catch exception from next() and return normally
268 return
Path 7: 1 calls (0.06)
['', ' 1. Beautiful is beTTer than ugly.', ' 2. Explicit is better than implicit.', ' 3. Simple is better than complex.', ' 4. Complex is bett...
['', ' 1. Beautiful is beTTer than ugly.', ' 2. Explicit is better than implicit.', ' 3. Simple is better than complex.', ' 4. Complex is bett...
5 (1)
None (1)
IS_CHARACTER_JUNK def (1)
None (1)
StopIteration (1)
1def _mdiff(fromlines, tolines, context=None, linejunk=None,
2 charjunk=IS_CHARACTER_JUNK):
3 r"""Returns generator yielding marked up from/to side by side differences.
4
5 Arguments:
6 fromlines -- list of text lines to compared to tolines
7 tolines -- list of text lines to be compared to fromlines
8 context -- number of context lines to display on each side of difference,
9 if None, all from/to text lines will be generated.
10 linejunk -- passed on to ndiff (see ndiff documentation)
11 charjunk -- passed on to ndiff (see ndiff documentation)
12
13 This function returns an iterator which returns a tuple:
14 (from line tuple, to line tuple, boolean flag)
15
16 from/to line tuple -- (line num, line text)
17 line num -- integer or None (to indicate a context separation)
18 line text -- original line text with following markers inserted:
19 '\0+' -- marks start of added text
20 '\0-' -- marks start of deleted text
21 '\0^' -- marks start of changed text
22 '\1' -- marks end of added/deleted/changed text
23
24 boolean flag -- None indicates context separation, True indicates
25 either "from" or "to" line contains a change, otherwise False.
26
27 This function/iterator was originally developed to generate side by side
28 file difference for making HTML pages (see HtmlDiff class for example
29 usage).
30
31 Note, this function utilizes the ndiff function to generate the side by
32 side difference markup. Optional ndiff arguments may be passed to this
33 function and they in turn will be passed to ndiff.
34 """
35 import re
36
37 # regular expression for finding intraline change indices
38 change_re = re.compile(r'(\++|\-+|\^+)')
39
40 # create the difference iterator to generate the differences
41 diff_lines_iterator = ndiff(fromlines,tolines,linejunk,charjunk)
42
43 def _make_line(lines, format_key, side, num_lines=[0,0]):
44 """Returns line of text with user's change markup and line formatting.
45
46 lines -- list of lines from the ndiff generator to produce a line of
47 text from. When producing the line of text to return, the
48 lines used are removed from this list.
49 format_key -- '+' return first line in list with "add" markup around
50 the entire line.
51 '-' return first line in list with "delete" markup around
52 the entire line.
53 '?' return first line in list with add/delete/change
54 intraline markup (indices obtained from second line)
55 None return first line in list with no markup
56 side -- indice into the num_lines list (0=from,1=to)
57 num_lines -- from/to current line number. This is NOT intended to be a
58 passed parameter. It is present as a keyword argument to
59 maintain memory of the current line numbers between calls
60 of this function.
61
62 Note, this function is purposefully not defined at the module scope so
63 that data it needs from its parent function (within whose context it
64 is defined) does not need to be of module scope.
65 """
66 num_lines[side] += 1
67 # Handle case where no user markup is to be added, just return line of
68 # text with user's line format to allow for usage of the line number.
69 if format_key is None:
70 return (num_lines[side],lines.pop(0)[2:])
71 # Handle case of intraline changes
72 if format_key == '?':
73 text, markers = lines.pop(0), lines.pop(0)
74 # find intraline changes (store change type and indices in tuples)
75 sub_info = []
76 def record_sub_info(match_object,sub_info=sub_info):
77 sub_info.append([match_object.group(1)[0],match_object.span()])
78 return match_object.group(1)
79 change_re.sub(record_sub_info,markers)
80 # process each tuple inserting our special marks that won't be
81 # noticed by an xml/html escaper.
82 for key,(begin,end) in reversed(sub_info):
83 text = text[0:begin]+'\0'+key+text[begin:end]+'\1'+text[end:]
84 text = text[2:]
85 # Handle case of add/delete entire line
86 else:
87 text = lines.pop(0)[2:]
88 # if line of text is just a newline, insert a space so there is
89 # something for the user to highlight and see.
90 if not text:
91 text = ' '
92 # insert marks that won't be noticed by an xml/html escaper.
93 text = '\0' + format_key + text + '\1'
94 # Return line of text, first allow user's line formatter to do its
95 # thing (such as adding the line number) then replace the special
96 # marks with what the user's change markup.
97 return (num_lines[side],text)
98
99 def _line_iterator():
100 """Yields from/to lines of text with a change indication.
101
102 This function is an iterator. It itself pulls lines from a
103 differencing iterator, processes them and yields them. When it can
104 it yields both a "from" and a "to" line, otherwise it will yield one
105 or the other. In addition to yielding the lines of from/to text, a
106 boolean flag is yielded to indicate if the text line(s) have
107 differences in them.
108
109 Note, this function is purposefully not defined at the module scope so
110 that data it needs from its parent function (within whose context it
111 is defined) does not need to be of module scope.
112 """
113 lines = []
114 num_blanks_pending, num_blanks_to_yield = 0, 0
115 while True:
116 # Load up next 4 lines so we can look ahead, create strings which
117 # are a concatenation of the first character of each of the 4 lines
118 # so we can do some very readable comparisons.
119 while len(lines) < 4:
120 lines.append(next(diff_lines_iterator, 'X'))
121 s = ''.join([line[0] for line in lines])
122 if s.startswith('X'):
123 # When no more lines, pump out any remaining blank lines so the
124 # corresponding add/delete lines get a matching blank line so
125 # all line pairs get yielded at the next level.
126 num_blanks_to_yield = num_blanks_pending
127 elif s.startswith('-?+?'):
128 # simple intraline change
129 yield _make_line(lines,'?',0), _make_line(lines,'?',1), True
130 continue
131 elif s.startswith('--++'):
132 # in delete block, add block coming: we do NOT want to get
133 # caught up on blank lines yet, just process the delete line
134 num_blanks_pending -= 1
135 yield _make_line(lines,'-',0), None, True
136 continue
137 elif s.startswith(('--?+', '--+', '- ')):
138 # in delete block and see an intraline change or unchanged line
139 # coming: yield the delete line and then blanks
140 from_line,to_line = _make_line(lines,'-',0), None
141 num_blanks_to_yield,num_blanks_pending = num_blanks_pending-1,0
142 elif s.startswith('-+?'):
143 # intraline change
144 yield _make_line(lines,None,0), _make_line(lines,'?',1), True
145 continue
146 elif s.startswith('-?+'):
147 # intraline change
148 yield _make_line(lines,'?',0), _make_line(lines,None,1), True
149 continue
150 elif s.startswith('-'):
151 # delete FROM line
152 num_blanks_pending -= 1
153 yield _make_line(lines,'-',0), None, True
154 continue
155 elif s.startswith('+--'):
156 # in add block, delete block coming: we do NOT want to get
157 # caught up on blank lines yet, just process the add line
158 num_blanks_pending += 1
159 yield None, _make_line(lines,'+',1), True
160 continue
161 elif s.startswith(('+ ', '+-')):
162 # will be leaving an add block: yield blanks then add line
163 from_line, to_line = None, _make_line(lines,'+',1)
164 num_blanks_to_yield,num_blanks_pending = num_blanks_pending+1,0
165 elif s.startswith('+'):
166 # inside an add block, yield the add line
167 num_blanks_pending += 1
168 yield None, _make_line(lines,'+',1), True
169 continue
170 elif s.startswith(' '):
171 # unchanged text, yield it to both sides
172 yield _make_line(lines[:],None,0),_make_line(lines,None,1),False
173 continue
174 # Catch up on the blank lines so when we yield the next from/to
175 # pair, they are lined up.
176 while(num_blanks_to_yield < 0):
177 num_blanks_to_yield += 1
178 yield None,('','\n'),True
179 while(num_blanks_to_yield > 0):
180 num_blanks_to_yield -= 1
181 yield ('','\n'),None,True
182 if s.startswith('X'):
183 return
184 else:
185 yield from_line,to_line,True
186
187 def _line_pair_iterator():
188 """Yields from/to lines of text with a change indication.
189
190 This function is an iterator. It itself pulls lines from the line
191 iterator. Its difference from that iterator is that this function
192 always yields a pair of from/to text lines (with the change
193 indication). If necessary it will collect single from/to lines
194 until it has a matching pair from/to pair to yield.
195
196 Note, this function is purposefully not defined at the module scope so
197 that data it needs from its parent function (within whose context it
198 is defined) does not need to be of module scope.
199 """
200 line_iterator = _line_iterator()
201 fromlines,tolines=[],[]
202 while True:
203 # Collecting lines of text until we have a from/to pair
204 while (len(fromlines)==0 or len(tolines)==0):
205 try:
206 from_line, to_line, found_diff = next(line_iterator)
207 except StopIteration:
208 return
209 if from_line is not None:
210 fromlines.append((from_line,found_diff))
211 if to_line is not None:
212 tolines.append((to_line,found_diff))
213 # Once we have a pair, remove them from the collection and yield it
214 from_line, fromDiff = fromlines.pop(0)
215 to_line, to_diff = tolines.pop(0)
216 yield (from_line,to_line,fromDiff or to_diff)
217
218 # Handle case where user does not want context differencing, just yield
219 # them up without doing anything else with them.
220 line_pair_iterator = _line_pair_iterator()
221 if context is None:
222 yield from line_pair_iterator
223 # Handle case where user wants context differencing. We must do some
224 # storage of lines until we know for sure that they are to be yielded.
225 else:
226 context += 1
227 lines_to_write = 0
228 while True:
229 # Store lines up until we find a difference, note use of a
230 # circular queue because we only need to keep around what
231 # we need for context.
232 index, contextLines = 0, [None]*(context)
233 found_diff = False
234 while(found_diff is False):
235 try:
236 from_line, to_line, found_diff = next(line_pair_iterator)
237 except StopIteration:
238 return
239 i = index % context
240 contextLines[i] = (from_line, to_line, found_diff)
241 index += 1
242 # Yield lines that we have collected so far, but first yield
243 # the user's separator.
244 if index > context:
245 yield None, None, None
246 lines_to_write = context
247 else:
248 lines_to_write = index
249 index = 0
250 while(lines_to_write):
251 i = index % context
252 index += 1
253 yield contextLines[i]
254 lines_to_write -= 1
255 # Now yield the context lines after the change
256 lines_to_write = context-1
257 try:
258 while(lines_to_write):
259 from_line, to_line, found_diff = next(line_pair_iterator)
260 # If another change within the context, extend the context
261 if found_diff:
262 lines_to_write = context-1
263 else:
264 lines_to_write -= 1
265 yield from_line, to_line, found_diff
266 except StopIteration:
267 # Catch exception from next() and return normally
268 return
Path 8: 1 calls (0.06)
[] (1)
[] (1)
5 (1)
None (1)
IS_CHARACTER_JUNK def (1)
None (1)
StopIteration (1)
1def _mdiff(fromlines, tolines, context=None, linejunk=None,
2 charjunk=IS_CHARACTER_JUNK):
3 r"""Returns generator yielding marked up from/to side by side differences.
4
5 Arguments:
6 fromlines -- list of text lines to compared to tolines
7 tolines -- list of text lines to be compared to fromlines
8 context -- number of context lines to display on each side of difference,
9 if None, all from/to text lines will be generated.
10 linejunk -- passed on to ndiff (see ndiff documentation)
11 charjunk -- passed on to ndiff (see ndiff documentation)
12
13 This function returns an iterator which returns a tuple:
14 (from line tuple, to line tuple, boolean flag)
15
16 from/to line tuple -- (line num, line text)
17 line num -- integer or None (to indicate a context separation)
18 line text -- original line text with following markers inserted:
19 '\0+' -- marks start of added text
20 '\0-' -- marks start of deleted text
21 '\0^' -- marks start of changed text
22 '\1' -- marks end of added/deleted/changed text
23
24 boolean flag -- None indicates context separation, True indicates
25 either "from" or "to" line contains a change, otherwise False.
26
27 This function/iterator was originally developed to generate side by side
28 file difference for making HTML pages (see HtmlDiff class for example
29 usage).
30
31 Note, this function utilizes the ndiff function to generate the side by
32 side difference markup. Optional ndiff arguments may be passed to this
33 function and they in turn will be passed to ndiff.
34 """
35 import re
36
37 # regular expression for finding intraline change indices
38 change_re = re.compile(r'(\++|\-+|\^+)')
39
40 # create the difference iterator to generate the differences
41 diff_lines_iterator = ndiff(fromlines,tolines,linejunk,charjunk)
42
43 def _make_line(lines, format_key, side, num_lines=[0,0]):
44 """Returns line of text with user's change markup and line formatting.
45
46 lines -- list of lines from the ndiff generator to produce a line of
47 text from. When producing the line of text to return, the
48 lines used are removed from this list.
49 format_key -- '+' return first line in list with "add" markup around
50 the entire line.
51 '-' return first line in list with "delete" markup around
52 the entire line.
53 '?' return first line in list with add/delete/change
54 intraline markup (indices obtained from second line)
55 None return first line in list with no markup
56 side -- indice into the num_lines list (0=from,1=to)
57 num_lines -- from/to current line number. This is NOT intended to be a
58 passed parameter. It is present as a keyword argument to
59 maintain memory of the current line numbers between calls
60 of this function.
61
62 Note, this function is purposefully not defined at the module scope so
63 that data it needs from its parent function (within whose context it
64 is defined) does not need to be of module scope.
65 """
66 num_lines[side] += 1
67 # Handle case where no user markup is to be added, just return line of
68 # text with user's line format to allow for usage of the line number.
69 if format_key is None:
70 return (num_lines[side],lines.pop(0)[2:])
71 # Handle case of intraline changes
72 if format_key == '?':
73 text, markers = lines.pop(0), lines.pop(0)
74 # find intraline changes (store change type and indices in tuples)
75 sub_info = []
76 def record_sub_info(match_object,sub_info=sub_info):
77 sub_info.append([match_object.group(1)[0],match_object.span()])
78 return match_object.group(1)
79 change_re.sub(record_sub_info,markers)
80 # process each tuple inserting our special marks that won't be
81 # noticed by an xml/html escaper.
82 for key,(begin,end) in reversed(sub_info):
83 text = text[0:begin]+'\0'+key+text[begin:end]+'\1'+text[end:]
84 text = text[2:]
85 # Handle case of add/delete entire line
86 else:
87 text = lines.pop(0)[2:]
88 # if line of text is just a newline, insert a space so there is
89 # something for the user to highlight and see.
90 if not text:
91 text = ' '
92 # insert marks that won't be noticed by an xml/html escaper.
93 text = '\0' + format_key + text + '\1'
94 # Return line of text, first allow user's line formatter to do its
95 # thing (such as adding the line number) then replace the special
96 # marks with what the user's change markup.
97 return (num_lines[side],text)
98
99 def _line_iterator():
100 """Yields from/to lines of text with a change indication.
101
102 This function is an iterator. It itself pulls lines from a
103 differencing iterator, processes them and yields them. When it can
104 it yields both a "from" and a "to" line, otherwise it will yield one
105 or the other. In addition to yielding the lines of from/to text, a
106 boolean flag is yielded to indicate if the text line(s) have
107 differences in them.
108
109 Note, this function is purposefully not defined at the module scope so
110 that data it needs from its parent function (within whose context it
111 is defined) does not need to be of module scope.
112 """
113 lines = []
114 num_blanks_pending, num_blanks_to_yield = 0, 0
115 while True:
116 # Load up next 4 lines so we can look ahead, create strings which
117 # are a concatenation of the first character of each of the 4 lines
118 # so we can do some very readable comparisons.
119 while len(lines) < 4:
120 lines.append(next(diff_lines_iterator, 'X'))
121 s = ''.join([line[0] for line in lines])
122 if s.startswith('X'):
123 # When no more lines, pump out any remaining blank lines so the
124 # corresponding add/delete lines get a matching blank line so
125 # all line pairs get yielded at the next level.
126 num_blanks_to_yield = num_blanks_pending
127 elif s.startswith('-?+?'):
128 # simple intraline change
129 yield _make_line(lines,'?',0), _make_line(lines,'?',1), True
130 continue
131 elif s.startswith('--++'):
132 # in delete block, add block coming: we do NOT want to get
133 # caught up on blank lines yet, just process the delete line
134 num_blanks_pending -= 1
135 yield _make_line(lines,'-',0), None, True
136 continue
137 elif s.startswith(('--?+', '--+', '- ')):
138 # in delete block and see an intraline change or unchanged line
139 # coming: yield the delete line and then blanks
140 from_line,to_line = _make_line(lines,'-',0), None
141 num_blanks_to_yield,num_blanks_pending = num_blanks_pending-1,0
142 elif s.startswith('-+?'):
143 # intraline change
144 yield _make_line(lines,None,0), _make_line(lines,'?',1), True
145 continue
146 elif s.startswith('-?+'):
147 # intraline change
148 yield _make_line(lines,'?',0), _make_line(lines,None,1), True
149 continue
150 elif s.startswith('-'):
151 # delete FROM line
152 num_blanks_pending -= 1
153 yield _make_line(lines,'-',0), None, True
154 continue
155 elif s.startswith('+--'):
156 # in add block, delete block coming: we do NOT want to get
157 # caught up on blank lines yet, just process the add line
158 num_blanks_pending += 1
159 yield None, _make_line(lines,'+',1), True
160 continue
161 elif s.startswith(('+ ', '+-')):
162 # will be leaving an add block: yield blanks then add line
163 from_line, to_line = None, _make_line(lines,'+',1)
164 num_blanks_to_yield,num_blanks_pending = num_blanks_pending+1,0
165 elif s.startswith('+'):
166 # inside an add block, yield the add line
167 num_blanks_pending += 1
168 yield None, _make_line(lines,'+',1), True
169 continue
170 elif s.startswith(' '):
171 # unchanged text, yield it to both sides
172 yield _make_line(lines[:],None,0),_make_line(lines,None,1),False
173 continue
174 # Catch up on the blank lines so when we yield the next from/to
175 # pair, they are lined up.
176 while(num_blanks_to_yield < 0):
177 num_blanks_to_yield += 1
178 yield None,('','\n'),True
179 while(num_blanks_to_yield > 0):
180 num_blanks_to_yield -= 1
181 yield ('','\n'),None,True
182 if s.startswith('X'):
183 return
184 else:
185 yield from_line,to_line,True
186
187 def _line_pair_iterator():
188 """Yields from/to lines of text with a change indication.
189
190 This function is an iterator. It itself pulls lines from the line
191 iterator. Its difference from that iterator is that this function
192 always yields a pair of from/to text lines (with the change
193 indication). If necessary it will collect single from/to lines
194 until it has a matching pair from/to pair to yield.
195
196 Note, this function is purposefully not defined at the module scope so
197 that data it needs from its parent function (within whose context it
198 is defined) does not need to be of module scope.
199 """
200 line_iterator = _line_iterator()
201 fromlines,tolines=[],[]
202 while True:
203 # Collecting lines of text until we have a from/to pair
204 while (len(fromlines)==0 or len(tolines)==0):
205 try:
206 from_line, to_line, found_diff = next(line_iterator)
207 except StopIteration:
208 return
209 if from_line is not None:
210 fromlines.append((from_line,found_diff))
211 if to_line is not None:
212 tolines.append((to_line,found_diff))
213 # Once we have a pair, remove them from the collection and yield it
214 from_line, fromDiff = fromlines.pop(0)
215 to_line, to_diff = tolines.pop(0)
216 yield (from_line,to_line,fromDiff or to_diff)
217
218 # Handle case where user does not want context differencing, just yield
219 # them up without doing anything else with them.
220 line_pair_iterator = _line_pair_iterator()
221 if context is None:
222 yield from line_pair_iterator
223 # Handle case where user wants context differencing. We must do some
224 # storage of lines until we know for sure that they are to be yielded.
225 else:
226 context += 1
227 lines_to_write = 0
228 while True:
229 # Store lines up until we find a difference, note use of a
230 # circular queue because we only need to keep around what
231 # we need for context.
232 index, contextLines = 0, [None]*(context)
233 found_diff = False
234 while(found_diff is False):
235 try:
236 from_line, to_line, found_diff = next(line_pair_iterator)
237 except StopIteration:
238 return
239 i = index % context
240 contextLines[i] = (from_line, to_line, found_diff)
241 index += 1
242 # Yield lines that we have collected so far, but first yield
243 # the user's separator.
244 if index > context:
245 yield None, None, None
246 lines_to_write = context
247 else:
248 lines_to_write = index
249 index = 0
250 while(lines_to_write):
251 i = index % context
252 index += 1
253 yield contextLines[i]
254 lines_to_write -= 1
255 # Now yield the context lines after the change
256 lines_to_write = context-1
257 try:
258 while(lines_to_write):
259 from_line, to_line, found_diff = next(line_pair_iterator)
260 # If another change within the context, extend the context
261 if found_diff:
262 lines_to_write = context-1
263 else:
264 lines_to_write -= 1
265 yield from_line, to_line, found_diff
266 except StopIteration:
267 # Catch exception from next() and return normally
268 return