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