def diff_blocks(fromlines, tolines, context=None, tabwidth=8, ignore_blank_lines=0, ignore_case=0, ignore_space_changes=0): """Return an array that is adequate for adding to the data dictionary See `get_filtered_hunks` for the parameter descriptions. See also the diff_div.html template. """ type_map = { 'replace': 'mod', 'delete': 'rem', 'insert': 'add', 'equal': 'unmod' } space_re = re.compile(' ( +)|^ ') def htmlify(match): div, mod = divmod(len(match.group(0)), 2) return div * ' ' + mod * ' ' def markup_intraline_changes(opcodes): for tag, i1, i2, j1, j2 in opcodes: if tag == 'replace' and i2 - i1 == j2 - j1: for i in range(i2 - i1): fromline, toline = fromlines[i1 + i], tolines[j1 + i] (start, end) = get_change_extent(fromline, toline) if start != 0 or end != 0: last = end + len(fromline) fromlines[i1 + i] = (fromline[:start] + '\0' + fromline[start:last] + '\1' + fromline[last:]) last = end + len(toline) tolines[j1 + i] = (toline[:start] + '\0' + toline[start:last] + '\1' + toline[last:]) yield tag, i1, i2, j1, j2 changes = [] for group in get_filtered_hunks(fromlines, tolines, context, ignore_blank_lines, ignore_case, ignore_space_changes): blocks = [] last_tag = None for tag, i1, i2, j1, j2 in markup_intraline_changes(group): if tag != last_tag: blocks.append({ 'type': type_map[tag], 'base': { 'offset': i1, 'lines': [] }, 'changed': { 'offset': j1, 'lines': [] } }) if tag == 'equal': for line in fromlines[i1:i2]: line = line.expandtabs(tabwidth) line = space_re.sub(htmlify, escape(line, quotes=False)) blocks[-1]['base']['lines'].append(Markup(unicode(line))) for line in tolines[j1:j2]: line = line.expandtabs(tabwidth) line = space_re.sub(htmlify, escape(line, quotes=False)) blocks[-1]['changed']['lines'].append(Markup( unicode(line))) else: if tag in ('replace', 'delete'): for line in fromlines[i1:i2]: line = expandtabs(line, tabwidth, '\0\1') line = escape(line, quotes=False) line = '<del>'.join([ space_re.sub(htmlify, seg) for seg in line.split('\0') ]) line = line.replace('\1', '</del>') blocks[-1]['base']['lines'].append( Markup(unicode(line))) if tag in ('replace', 'insert'): for line in tolines[j1:j2]: line = expandtabs(line, tabwidth, '\0\1') line = escape(line, quotes=False) line = '<ins>'.join([ space_re.sub(htmlify, seg) for seg in line.split('\0') ]) line = line.replace('\1', '</ins>') blocks[-1]['changed']['lines'].append( Markup(unicode(line))) changes.append(blocks) return changes
def _diff_to_hdf(self, difflines, tabwidth): """ Translate a diff file into something suitable for inclusion in HDF. The result is [(filename, revname_old, revname_new, changes)], where changes has the same format as the result of `trac.versioncontrol.diff.hdf_diff`. If the diff cannot be parsed, this method returns None. """ def _markup_intraline_change(fromlines, tolines): from trac.versioncontrol.diff import get_change_extent for i in xrange(len(fromlines)): fr, to = fromlines[i], tolines[i] (start, end) = get_change_extent(fr, to) if start != 0 or end != 0: last = end + len(fr) fromlines[i] = fr[:start] + '\0' + fr[start:last] + \ '\1' + fr[last:] last = end + len(to) tolines[i] = to[:start] + '\0' + to[start:last] + \ '\1' + to[last:] space_re = re.compile(' ( +)|^ ') def htmlify(match): div, mod = divmod(len(match.group(0)), 2) return Markup(div * ' ' + mod * ' ') comments = [] changes = [] lines = iter(difflines) try: line = next(lines) while True: oldpath = oldrev = newpath = newrev = '' oldinfo = newinfo = [] binary = False # consume preamble, storing free lines in comments # (also detect the special case of git binary patches) if not line.startswith('--- '): if not line.startswith('Index: ') and line != '=' * 67: comments.append(line) if line == "GIT binary patch": binary = True diffcmd_line = comments[0] # diff --git a/... b/,,, oldpath, newpath = diffcmd_line.split()[-2:] if any(c.startswith('new file') for c in comments): oldpath = '/dev/null' if any(c.startswith('deleted file') for c in comments): newpath = '/dev/null' oldinfo = ['', oldpath] newinfo = ['', newpath] index = [c for c in comments if c.startswith('index ')] if index: # index 8f****78..1e****5c oldrev, newrev = index[0].split()[-1].split('..') oldinfo.append(oldrev) newinfo.append(newrev) line = next(lines) while line: comments.append(line) line = next(lines) else: line = next(lines) continue if not oldinfo and not newinfo: # Base filename/version from '--- <file> [rev]' oldinfo = line.split(None, 2) if len(oldinfo) > 1: oldpath = oldinfo[1] if len(oldinfo) > 2: oldrev = oldinfo[2] # Changed filename/version from '+++ <file> [rev]' line = next(lines) if not line.startswith('+++ '): self.log.debug('expected +++ after ---, got %s', line) return None newinfo = line.split(None, 2) if len(newinfo) > 1: newpath = newinfo[1] if len(newinfo) > 2: newrev = newinfo[2] shortrev = ('old', 'new') if oldpath or newpath: sep = re.compile(r'([/.~\\])') commonprefix = ''.join( os.path.commonprefix( [sep.split(newpath), sep.split(oldpath)])) commonsuffix = ''.join( os.path.commonprefix([ sep.split(newpath)[::-1], sep.split(oldpath)[::-1] ])[::-1]) if len(commonprefix) > len(commonsuffix): common = commonprefix elif commonsuffix: common = commonsuffix.lstrip('/') a = oldpath[:-len(commonsuffix)] b = newpath[:-len(commonsuffix)] if len(a) < 4 and len(b) < 4: shortrev = (a, b) elif oldpath == '/dev/null': common = _("new file %(new)s", new=newpath.lstrip('b/')) shortrev = ('-', '+') elif newpath == '/dev/null': common = _("deleted file %(deleted)s", deleted=oldpath.lstrip('a/')) shortrev = ('+', '-') else: common = '(a) %s vs. (b) %s' % (oldpath, newpath) shortrev = ('a', 'b') else: common = '' groups = [] groups_title = [] changes.append({ 'change': 'edit', 'props': [], 'comments': '\n'.join(comments), 'binary': binary, 'diffs': groups, 'diffs_title': groups_title, 'old': { 'path': common, 'rev': ' '.join(oldinfo[1:]), 'shortrev': shortrev[0] }, 'new': { 'path': common, 'rev': ' '.join(newinfo[1:]), 'shortrev': shortrev[1] } }) comments = [] line = next(lines) while line: # "@@ -333,10 +329,8 @@" or "@@ -1 +1 @@ [... title ...]" r = re.match( r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@' '(.*)', line) if not r: break blocks = [] groups.append(blocks) fromline, fromend, toline, toend = \ [int(x or 1) for x in r.groups()[:4]] groups_title.append(r.group(5)) last_type = extra = None fromend += fromline toend += toline line = next(lines) while fromline < fromend or toline < toend or extra: # First character is the command command = ' ' if line: command, line = line[0], line[1:] # Make a new block? if (command == ' ') != last_type: last_type = command == ' ' kind = 'unmod' if last_type else 'mod' block = { 'type': kind, 'base': { 'offset': fromline - 1, 'lines': [] }, 'changed': { 'offset': toline - 1, 'lines': [] } } blocks.append(block) else: block = blocks[-1] if command == ' ': sides = ['base', 'changed'] elif command == '+': last_side = 'changed' sides = [last_side] elif command == '-': last_side = 'base' sides = [last_side] elif command == '\\' and last_side: meta = block[last_side].setdefault('meta', {}) meta[len(block[last_side]['lines'])] = True sides = [last_side] elif command == '@': # ill-formed patch groups_title[-1] = "%s (%s)" % ( groups_title[-1], _("this hunk was shorter than expected")) line = '@' + line break else: self.log.debug('expected +, - or \\, got %s', command) return None for side in sides: if side == 'base': fromline += 1 else: toline += 1 block[side]['lines'].append(line) line = next(lines) extra = line and line[0] == '\\' except StopIteration: pass # Go through all groups/blocks and mark up intraline changes, and # convert to html for o in changes: for group in o['diffs']: for b in group: base, changed = b['base'], b['changed'] f, t = base['lines'], changed['lines'] if b['type'] == 'mod': if len(f) == 0: b['type'] = 'add' elif len(t) == 0: b['type'] = 'rem' elif len(f) == len(t): _markup_intraline_change(f, t) for i in xrange(len(f)): line = expandtabs(f[i], tabwidth, '\0\1') line = escape(line, quotes=False) line = '<del>'.join( space_re.sub(htmlify, seg) for seg in line.split('\0')) line = line.replace('\1', '</del>') f[i] = Markup(line) if 'meta' in base and i in base['meta']: f[i] = Markup('<em>%s</em>') % f[i] for i in xrange(len(t)): line = expandtabs(t[i], tabwidth, '\0\1') line = escape(line, quotes=False) line = '<ins>'.join( space_re.sub(htmlify, seg) for seg in line.split('\0')) line = line.replace('\1', '</ins>') t[i] = Markup(line) if 'meta' in changed and i in changed['meta']: t[i] = Markup('<em>%s</em>') % t[i] return changes
def _diff_to_hdf(self, difflines, tabwidth): """ Translate a diff file into something suitable for inclusion in HDF. The result is [(filename, revname_old, revname_new, changes)], where changes has the same format as the result of `trac.versioncontrol.diff.hdf_diff`. If the diff cannot be parsed, this method returns None. """ def _markup_intraline_change(fromlines, tolines): from trac.versioncontrol.diff import get_change_extent for i in xrange(len(fromlines)): fr, to = fromlines[i], tolines[i] (start, end) = get_change_extent(fr, to) if start != 0 or end != 0: last = end+len(fr) fromlines[i] = fr[:start] + '\0' + fr[start:last] + \ '\1' + fr[last:] last = end+len(to) tolines[i] = to[:start] + '\0' + to[start:last] + \ '\1' + to[last:] import re space_re = re.compile(' ( +)|^ ') def htmlify(match): div, mod = divmod(len(match.group(0)), 2) return div * ' ' + mod * ' ' comments = [] changes = [] lines = iter(difflines) try: line = lines.next() while True: oldpath = oldrev = newpath = newrev = '' oldinfo = newinfo = [] binary = False # consume preample, storing free lines in comments # (also detect the special case of git binary patches) if not line.startswith('--- '): if not line.startswith('Index: ') and line != '=' * 67: comments.append(line) if line == "GIT binary patch": binary = True diffcmd_line = comments[0] # diff --git a/... b/,,, oldpath, newpath = diffcmd_line.split()[-2:] if any(c.startswith('new file') for c in comments): oldpath = '/dev/null' if any(c.startswith('deleted file') for c in comments): newpath = '/dev/null' oldinfo = ['', oldpath] newinfo = ['', newpath] index = [c for c in comments if c.startswith('index ')] if index: # index 8f****78..1e****5c oldrev, newrev = index[0].split()[-1].split('..') oldinfo.append(oldrev) newinfo.append(newrev) line = lines.next() while line: comments.append(line) line = lines.next() else: line = lines.next() continue if not oldinfo and not newinfo: # Base filename/version from '--- <file> [rev]' oldinfo = line.split(None, 2) if len(oldinfo) > 1: oldpath = oldinfo[1] if len(oldinfo) > 2: oldrev = oldinfo[2] # Changed filename/version from '+++ <file> [rev]' line = lines.next() if not line.startswith('+++ '): self.log.debug('expected +++ after ---, got ' + line) return None newinfo = line.split(None, 2) if len(newinfo) > 1: newpath = newinfo[1] if len(newinfo) > 2: newrev = newinfo[2] shortrev = ('old', 'new') if oldpath or newpath: sep = re.compile(r'([/.~\\])') commonprefix = ''.join(os.path.commonprefix( [sep.split(newpath), sep.split(oldpath)])) commonsuffix = ''.join(os.path.commonprefix( [sep.split(newpath)[::-1], sep.split(oldpath)[::-1]])[::-1]) if len(commonprefix) > len(commonsuffix): common = commonprefix elif commonsuffix: common = commonsuffix.lstrip('/') a = oldpath[:-len(commonsuffix)] b = newpath[:-len(commonsuffix)] if len(a) < 4 and len(b) < 4: shortrev = (a, b) elif oldpath == '/dev/null': common = _("new file %(new)s", new=newpath.lstrip('b/')) shortrev = ('-', '+') elif newpath == '/dev/null': common = _("deleted file %(deleted)s", deleted=oldpath.lstrip('a/')) shortrev = ('+', '-') else: common = '(a) %s vs. (b) %s' % (oldpath, newpath) shortrev = ('a', 'b') else: common = '' groups = [] groups_title = [] changes.append({'change': 'edit', 'props': [], 'comments': '\n'.join(comments), 'binary': binary, 'diffs': groups, 'diffs_title': groups_title, 'old': {'path': common, 'rev': ' '.join(oldinfo[1:]), 'shortrev': shortrev[0]}, 'new': {'path': common, 'rev': ' '.join(newinfo[1:]), 'shortrev': shortrev[1]}}) comments = [] line = lines.next() while line: # "@@ -333,10 +329,8 @@" or "@@ -1 +1 @@ [... title ...]" r = re.match(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@' '(.*)', line) if not r: break blocks = [] groups.append(blocks) fromline, fromend, toline, toend = \ [int(x or 1) for x in r.groups()[:4]] groups_title.append(r.group(5)) last_type = extra = None fromend += fromline toend += toline line = lines.next() while fromline < fromend or toline < toend or extra: # First character is the command command = ' ' if line: command, line = line[0], line[1:] # Make a new block? if (command == ' ') != last_type: last_type = command == ' ' kind = 'unmod' if last_type else 'mod' block = {'type': kind, 'base': {'offset': fromline - 1, 'lines': []}, 'changed': {'offset': toline - 1, 'lines': []}} blocks.append(block) else: block = blocks[-1] if command == ' ': sides = ['base', 'changed'] elif command == '+': last_side = 'changed' sides = [last_side] elif command == '-': last_side = 'base' sides = [last_side] elif command == '\\' and last_side: meta = block[last_side].setdefault('meta', {}) meta[len(block[last_side]['lines'])] = True sides = [last_side] elif command == '@': # ill-formed patch groups_title[-1] = "%s (%s)" % ( groups_title[-1], _("this hunk was shorter than expected")) line = '@'+line break else: self.log.debug('expected +, - or \\, got '+command) return None for side in sides: if side == 'base': fromline += 1 else: toline += 1 block[side]['lines'].append(line) line = lines.next() extra = line and line[0] == '\\' except StopIteration: pass # Go through all groups/blocks and mark up intraline changes, and # convert to html for o in changes: for group in o['diffs']: for b in group: base, changed = b['base'], b['changed'] f, t = base['lines'], changed['lines'] if b['type'] == 'mod': if len(f) == 0: b['type'] = 'add' elif len(t) == 0: b['type'] = 'rem' elif len(f) == len(t): _markup_intraline_change(f, t) for i in xrange(len(f)): line = expandtabs(f[i], tabwidth, '\0\1') line = escape(line, quotes=False) line = '<del>'.join([space_re.sub(htmlify, seg) for seg in line.split('\0')]) line = line.replace('\1', '</del>') f[i] = Markup(line) if 'meta' in base and i in base['meta']: f[i] = Markup('<em>%s</em>') % f[i] for i in xrange(len(t)): line = expandtabs(t[i], tabwidth, '\0\1') line = escape(line, quotes=False) line = '<ins>'.join([space_re.sub(htmlify, seg) for seg in line.split('\0')]) line = line.replace('\1', '</ins>') t[i] = Markup(line) if 'meta' in changed and i in changed['meta']: t[i] = Markup('<em>%s</em>') % t[i] return changes
def test_tabstops(self): self.assertEqual(' ', expandtabs(' \t')) self.assertEqual(' ', expandtabs('\t\t'))
def test_empty(self): x = expandtabs('', ignoring='\0') self.assertEqual('', x)
def diff_blocks(fromlines, tolines, context=None, tabwidth=8, ignore_blank_lines=0, ignore_case=0, ignore_space_changes=0): """Return an array that is adequate for adding to the data dictionary See `get_filtered_hunks` for the parameter descriptions. See also the diff_div.html template. """ type_map = {'replace': 'mod', 'delete': 'rem', 'insert': 'add', 'equal': 'unmod'} space_re = re.compile(' ( +)|^ ') def htmlify(match): div, mod = divmod(len(match.group(0)), 2) return div * ' ' + mod * ' ' def markup_intraline_changes(opcodes): for tag, i1, i2, j1, j2 in opcodes: if tag == 'replace' and i2 - i1 == j2 - j1: for i in range(i2 - i1): fromline, toline = fromlines[i1 + i], tolines[j1 + i] (start, end) = get_change_extent(fromline, toline) if start != 0 or end != 0: last = end + len(fromline) fromlines[i1 + i] = ( fromline[:start] + '\0' + fromline[start:last] + '\1' + fromline[last:]) last = end+len(toline) tolines[j1 + i] = ( toline[:start] + '\0' + toline[start:last] + '\1' + toline[last:]) yield tag, i1, i2, j1, j2 changes = [] for group in get_filtered_hunks(fromlines, tolines, context, ignore_blank_lines, ignore_case, ignore_space_changes): blocks = [] last_tag = None for tag, i1, i2, j1, j2 in markup_intraline_changes(group): if tag != last_tag: blocks.append({'type': type_map[tag], 'base': {'offset': i1, 'lines': []}, 'changed': {'offset': j1, 'lines': []}}) if tag == 'equal': for line in fromlines[i1:i2]: line = line.expandtabs(tabwidth) line = space_re.sub(htmlify, escape(line, quotes=False)) blocks[-1]['base']['lines'].append(Markup(unicode(line))) for line in tolines[j1:j2]: line = line.expandtabs(tabwidth) line = space_re.sub(htmlify, escape(line, quotes=False)) blocks[-1]['changed']['lines'].append(Markup(unicode(line))) else: if tag in ('replace', 'delete'): for line in fromlines[i1:i2]: line = expandtabs(line, tabwidth, '\0\1') line = escape(line, quotes=False) line = '<del>'.join([space_re.sub(htmlify, seg) for seg in line.split('\0')]) line = line.replace('\1', '</del>') blocks[-1]['base']['lines'].append( Markup(unicode(line))) if tag in ('replace', 'insert'): for line in tolines[j1:j2]: line = expandtabs(line, tabwidth, '\0\1') line = escape(line, quotes=False) line = '<ins>'.join([space_re.sub(htmlify, seg) for seg in line.split('\0')]) line = line.replace('\1', '</ins>') blocks[-1]['changed']['lines'].append( Markup(unicode(line))) changes.append(blocks) return changes
def test_ingoring(self): x = expandtabs('\0\t', ignoring='\0') self.assertEqual('\0 ', x)
def test_ingoring(self): x = expandtabs("\0\t", ignoring="\0") self.assertEquals("\0 ", x)
def test_tabstops(self): self.assertEquals(" ", expandtabs(" \t")) self.assertEquals(" ", expandtabs("\t\t"))
def test_empty(self): x = expandtabs("", ignoring="\0") self.assertEquals("", x)
def test_tabstops(self): self.assertEquals(' ', expandtabs(' \t')) self.assertEquals(' ', expandtabs('\t\t'))
def test_ingoring(self): x = expandtabs('\0\t', ignoring='\0') self.assertEquals('\0 ', x)
def test_empty(self): x = expandtabs('', ignoring='\0') self.assertEquals('', x)
def _diff_to_hdf(self, difflines, tabwidth): """ Translate a diff file into something suitable for inclusion in HDF. The result is [(filename, revname_old, revname_new, changes)], where changes has the same format as the result of `trac.versioncontrol.diff.hdf_diff`. If the diff cannot be parsed, this method returns None. """ def _markup_intraline_change(fromlines, tolines): from trac.versioncontrol.diff import _get_change_extent for i in xrange(len(fromlines)): fr, to = fromlines[i], tolines[i] (start, end) = _get_change_extent(fr, to) if start != 0 or end != 0: last = end+len(fr) fromlines[i] = fr[:start] + '\0' + fr[start:last] + \ '\1' + fr[last:] last = end+len(to) tolines[i] = to[:start] + '\0' + to[start:last] + \ '\1' + to[last:] import re space_re = re.compile(' ( +)|^ ') def htmlify(match): div, mod = divmod(len(match.group(0)), 2) return div * ' ' + mod * ' ' comments = [] changes = [] lines = iter(difflines) try: line = lines.next() while True: if not line.startswith('--- '): if not line.startswith('Index: ') and line != '='*67: comments.append(line) line = lines.next() continue oldpath = oldrev = newpath = newrev = '' # Base filename/version oldinfo = line.split(None, 2) if len(oldinfo) > 1: oldpath = oldinfo[1] if len(oldinfo) > 2: oldrev = oldinfo[2] # Changed filename/version line = lines.next() if not line.startswith('+++ '): self.log.debug('expected +++ after ---, got '+line) return None newinfo = line.split(None, 2) if len(newinfo) > 1: newpath = newinfo[1] if len(newinfo) > 2: newrev = newinfo[2] shortrev = ('old', 'new') if oldpath or newpath: sep = re.compile(r'([/.~\\])') commonprefix = ''.join(os.path.commonprefix( [sep.split(newpath), sep.split(oldpath)])) commonsuffix = ''.join(os.path.commonprefix( [sep.split(newpath)[::-1], sep.split(oldpath)[::-1]])[::-1]) if len(commonprefix) > len(commonsuffix): common = commonprefix elif commonsuffix: common = commonsuffix.lstrip('/') a = oldpath[:-len(commonsuffix)] b = newpath[:-len(commonsuffix)] if len(a) < 4 and len(b) < 4: shortrev = (a, b) else: common = '(a) %s vs. (b) %s' % (oldpath, newpath) shortrev = ('a', 'b') else: common = '' groups = [] changes.append({'change': 'edit', 'props': [], 'comments': '\n'.join(comments), 'diffs': groups, 'old': {'path': common, 'rev': ' '.join(oldinfo[1:]), 'shortrev': shortrev[0]}, 'new': {'path': common, 'rev': ' '.join(newinfo[1:]), 'shortrev': shortrev[1]}}) comments = [] line = lines.next() while line: # "@@ -333,10 +329,8 @@" or "@@ -1 +1 @@" r = re.match(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@', line) if not r: break blocks = [] groups.append(blocks) fromline, fromend, toline, toend = [int(x or 1) for x in r.groups()] last_type = last_change = extra = None fromend += fromline toend += toline line = lines.next() while fromline < fromend or toline < toend or extra: # First character is the command command = ' ' if line: command, line = line[0], line[1:] # Make a new block? if (command == ' ') != last_type: last_type = command == ' ' kind = last_type and 'unmod' or 'mod' block = {'type': kind, 'base': {'offset': fromline - 1, 'lines': []}, 'changed': {'offset': toline - 1, 'lines': []}} blocks.append(block) else: block = blocks[-1] if command == ' ': sides = ['base', 'changed'] elif command == '+': last_side = 'changed' sides = [last_side] elif command == '-': last_side = 'base' sides = [last_side] elif command == '\\' and last_side: meta = block[last_side].setdefault('meta', {}) meta[len(block[last_side]['lines'])] = True sides = [last_side] else: self.log.debug('expected +, - or \\, got '+command) return None for side in sides: if side == 'base': fromline += 1 else: toline += 1 block[side]['lines'].append(line) line = lines.next() extra = line and line[0] == '\\' except StopIteration: pass # Go through all groups/blocks and mark up intraline changes, and # convert to html for o in changes: for group in o['diffs']: for b in group: base, changed = b['base'], b['changed'] f, t = base['lines'], changed['lines'] if b['type'] == 'mod': if len(f) == 0: b['type'] = 'add' elif len(t) == 0: b['type'] = 'rem' elif len(f) == len(t): _markup_intraline_change(f, t) for i in xrange(len(f)): line = expandtabs(f[i], tabwidth, '\0\1') line = escape(line, quotes=False) line = '<del>'.join([space_re.sub(htmlify, seg) for seg in line.split('\0')]) line = line.replace('\1', '</del>') f[i] = Markup(line) if 'meta' in base and i in base['meta']: f[i] = Markup('<em>%s</em>') % f[i] for i in xrange(len(t)): line = expandtabs(t[i], tabwidth, '\0\1') line = escape(line, quotes=False) line = '<ins>'.join([space_re.sub(htmlify, seg) for seg in line.split('\0')]) line = line.replace('\1', '</ins>') t[i] = Markup(line) if 'meta' in changed and i in changed['meta']: t[i] = Markup('<em>%s</em>') % t[i] return changes