<commit_before># -*- coding: utf-8 -*-
#
# Copyright (C) 2005 Edgewall Software
# Copyright (C) 2005 <NAME> <<EMAIL>>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.com/license.html.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://projects.edgewall.com/trac/.
#
# Author: <NAME> <<EMAIL>>
#         <NAME>

from trac.core import *
from trac.mimeview.api import content_to_unicode, IHTMLPreviewRenderer, Mimeview
from trac.util.markup import escape, Markup
from trac.web.chrome import add_stylesheet

__all__ = ['PatchRenderer']


class PatchRenderer(Component):
    """Structured display of patches in unified diff format, similar to the
    layout provided by the changeset view.
    """

    implements(IHTMLPreviewRenderer)

    diff_cs = """
<?cs include:'macros.cs' ?>
<div class="diff"><ul class="entries"><?cs
 each:file = diff.files ?><li class="entry">
  <h2><?cs var:file.filename ?></h2>
  <table class="inline" summary="Differences" cellspacing="0">
   <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup>
   <thead><tr>
    <th><?cs var:file.oldrev ?></th>
    <th><?cs var:file.newrev ?></th>
    <th>&nbsp;</th>
   </tr></thead><?cs
   each:change = file.diff ?><?cs
    call:diff_display(change, diff.style) ?><?cs
    if:name(change) < len(file.diff) - 1 ?>
     <tbody class="skipped">
      <tr><th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td></tr>
     </tbody><?cs
    /if ?><?cs
   /each ?>
  </table>
 </li><?cs /each ?>
</ul></div>
""" # diff_cs

    # IHTMLPreviewRenderer methods

    def get_quality_ratio(self, mimetype):
        if mimetype == 'text/x-diff':
            return 8
        return 0

    def render(self, req, mimetype, content, filename=None, rev=None):
        from trac.web.clearsilver import HDFWrapper

        content = content_to_unicode(self.env, content, mimetype)
        d = self._diff_to_hdf(content.splitlines(),
                              Mimeview(self.env).tab_width)
        if not d:
            raise TracError, 'Invalid unified diff content'
        hdf = HDFWrapper(loadpaths=[self.env.get_templates_dir(),
                                    self.config.get('trac', 'templates_dir')])
        hdf['diff.files'] = d

        add_stylesheet(req, 'common/css/diff.css')
        return hdf.render(hdf.parse(self.diff_cs))

    # Internal methods

    # FIXME: This function should probably share more code with the
    #        trac.versioncontrol.diff module
    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 and end != 0:
                    fromlines[i] = fr[:start] + '\0' + fr[start:end+len(fr)] + \
                                   '\1' + fr[end:]
                    tolines[i] = to[:start] + '\0' + to[start:end+len(to)] + \
                                 '\1' + to[end:]

        import re
        space_re = re.compile(' ( +)|^ ')
        def htmlify(match):
            div, mod = divmod(len(match.group(0)), 2)
            return div * '&nbsp; ' + mod * '&nbsp;'

        output = []
        filename, groups = None, None
        lines = iter(difflines)
        for line in lines:
            if not line.startswith('--- '):
                continue

            # Base filename/version
            words = line.split(None, 2)
            filename, fromrev = words[1], 'old'
            groups, blocks = None, None

            # Changed filename/version
            line = lines.next()
            if not line.startswith('+++ '):
                return None

            words = line.split(None, 2)
            if len(words[1]) < len(filename):
                # Always use the shortest filename for display
                filename = words[1]
            groups = []
            output.append({'filename' : filename, 'oldrev' : fromrev,
                           'newrev' : 'new', 'diff' : groups})

            for line in lines:
                # @@ -333,10 +329,8 @@
                r = re.match(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@', line)
                if not r:
                    break
                blocks = []
                groups.append(blocks)
                fromline,fromend,toline,toend = map(int, r.groups())
                last_type = None

                fromend += fromline
                toend += toline

                while fromline < fromend or toline < toend:
                    line = lines.next()

                    # First character is the command
                    command, line = line[0], line[1:]
                    # Make a new block?
                    if (command == ' ') != last_type:
                        last_type = command == ' '
                        blocks.append({'type': last_type and 'unmod' or 'mod',
                                       'base.offset': fromline - 1,
                                       'base.lines': [],
                                       'changed.offset': toline - 1,
                                       'changed.lines': []})
                    if command == ' ':
                        blocks[-1]['changed.lines'].append(line)
                        blocks[-1]['base.lines'].append(line)
                        fromline += 1
                        toline += 1
                    elif command == '+':
                        blocks[-1]['changed.lines'].append(line)
                        toline += 1
                    elif command == '-':
                        blocks[-1]['base.lines'].append(line)
                        fromline += 1
                    else:
                        return None

        # Go through all groups/blocks and mark up intraline changes, and
        # convert to html
        for o in output:
            for group in o['diff']:
                for b in group:
                    f, t = b['base.lines'], b['changed.lines']
                    if b['type'] == 'mod':
                        if len(f) == 0:
                            b['type'] = 'add'<commit_msg>Make the patch renderer a bit more robust, like when the  line is missing between two consecutive diffs (see attachment:ticket:1890:authen.diff for example)<commit_after># -*- coding: utf-8 -*-
#
# Copyright (C) 2005 Edgewall Software
# Copyright (C) 2005 <NAME> <<EMAIL>>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.com/license.html.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://projects.edgewall.com/trac/.
#
# Author: <NAME> <<EMAIL>>
#         <NAME>

from trac.core import *
from trac.mimeview.api import content_to_unicode, IHTMLPreviewRenderer, Mimeview
from trac.util.markup import escape, Markup
from trac.web.chrome import add_stylesheet

__all__ = ['PatchRenderer']


class PatchRenderer(Component):
    """Structured display of patches in unified diff format, similar to the
    layout provided by the changeset view.
    """

    implements(IHTMLPreviewRenderer)

    diff_cs = """
<?cs include:'macros.cs' ?>
<div class="diff"><ul class="entries"><?cs
 each:file = diff.files ?><li class="entry">
  <h2><?cs var:file.filename ?></h2>
  <table class="inline" summary="Differences" cellspacing="0">
   <colgroup><col class="lineno" /><col class="lineno" /><col class="content" /></colgroup>
   <thead><tr>
    <th><?cs var:file.oldrev ?></th>
    <th><?cs var:file.newrev ?></th>
    <th>&nbsp;</th>
   </tr></thead><?cs
   each:change = file.diff ?><?cs
    call:diff_display(change, diff.style) ?><?cs
    if:name(change) < len(file.diff) - 1 ?>
     <tbody class="skipped">
      <tr><th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td></tr>
     </tbody><?cs
    /if ?><?cs
   /each ?>
  </table>
 </li><?cs /each ?>
</ul></div>
""" # diff_cs

    # IHTMLPreviewRenderer methods

    def get_quality_ratio(self, mimetype):
        if mimetype == 'text/x-diff':
            return 8
        return 0

    def render(self, req, mimetype, content, filename=None, rev=None):
        from trac.web.clearsilver import HDFWrapper

        content = content_to_unicode(self.env, content, mimetype)
        d = self._diff_to_hdf(content.splitlines(),
                              Mimeview(self.env).tab_width)
        if not d:
            raise TracError, 'Invalid unified diff content'
        hdf = HDFWrapper(loadpaths=[self.env.get_templates_dir(),
                                    self.config.get('trac', 'templates_dir')])
        hdf['diff.files'] = d

        add_stylesheet(req, 'common/css/diff.css')
        return hdf.render(hdf.parse(self.diff_cs))

    # Internal methods

    # FIXME: This function should probably share more code with the
    #        trac.versioncontrol.diff module
    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 and end != 0:
                    fromlines[i] = fr[:start] + '\0' + fr[start:end+len(fr)] + \
                                   '\1' + fr[end:]
                    tolines[i] = to[:start] + '\0' + to[start:end+len(to)] + \
                                 '\1' + to[end:]

        import re
        space_re = re.compile(' ( +)|^ ')
        def htmlify(match):
            div, mod = divmod(len(match.group(0)), 2)
            return div * '&nbsp; ' + mod * '&nbsp;'

        output = []
        filename, groups = None, None
        lines = iter(difflines)
        try:
            line = lines.next()
            while True:
                if not line.startswith('--- '):
                    line = lines.next()
                    continue

                # Base filename/version
                words = line.split(None, 2)
                filename, fromrev = words[1], 'old'
                groups, blocks = None, None

                # Changed filename/version
                line = lines.next()
                if not line.startswith('+++ '):
                    return None

                words = line.split(None, 2)
                if len(words[1]) < len(filename):
                    # Always use the shortest filename for display
                    filename = words[1]
                groups = []
                output.append({'filename' : filename, 'oldrev' : fromrev,
                               'newrev' : 'new', 'diff' : groups})

                for line in lines:
                    # @@ -333,10 +329,8 @@
                    r = re.match(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@', line)
                    if not r:
                        break
                    blocks = []
                    groups.append(blocks)
                    fromline,fromend,toline,toend = map(int, r.groups())
                    last_type = None

                    fromend += fromline
                    toend += toline

                    while fromline < fromend or toline < toend:
                        line = lines.next()

                        # First character is the command
                        command, line = line[0], line[1:]
                        # Make a new block?
                        if (command == ' ') != last_type:
                            last_type = command == ' '
                            blocks.append({'type': last_type and 'unmod' or 'mod',
                                           'base.offset': fromline - 1,
                                           'base.lines': [],
                                           'changed.offset': toline - 1,
                                           'changed.lines': []})
                        if command == ' ':
                            blocks[-1]['changed.lines'].append(line)
                            blocks[-1]['base.lines'].append(line)
                            fromline += 1
                            toline += 1
                        elif command == '+':
                            blocks[-1]['changed.lines'].append(line)
                            toline += 1
                        elif command == '-':
                            blocks[-1]['base.lines'].append(line)
                            fromline += 1
                        else:
                            return None
                line = lines.next()
        except StopIteration:
            pass

        # Go through all groups/blocks and mark up intraline changes, and
        # convert to html
        for o in output:
            for group in o['diff']:
                for b in group:
                    f, t = b['base.lines'], b['changed.lines']
                    if b['type'] == 'mod':
                        if len(f) == 0:
                            b['type'] = 'add'
{'fraction_non_alphanumeric': 0.1263180092787853, 'fraction_numerical': 0.007310558132995923, 'mean_word_length': 1.570834839176003, 'pattern_counts': {'":': 0, '<': 117, '<?xml version=': 0, '>': 109, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1841002', 'n_tokens_mistral': 4058, 'n_tokens_neox': 3807, 'n_words': 1046}