
supported_types = [
    (8, 'text/x-diff')
    ]

import re
from trac.util import escape
from trac.versioncontrol.diff import _get_change_extent

def _markup_intraline_change(fromlines,tolines):
    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:]


# 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 hdf_diff
# If it's unable to parse a file, None is returned

def diff_to_hdf(difflines, tabwidth):
    space_re = re.compile(' ( +)|^ ')
    def htmlify(match):
        div, mod = divmod(len(match.group(0)), 2)
        return div * '&nbsp; ' + mod * '&nbsp;'

    try:
        output = []
        filename,groups = None,None
        for line in difflines:
            # --- trac/Milestone.py	(revision 1520)
            if line.startswith('--- '):
                words = line.split(None,2)
                filename,fromrev = words[1],'old'
                groups,blocks = None,None
                continue
            # +++ trac/Milestone.py	(working copy)
            if line.startswith('+++ '):
                words = line.split(None,2)
                if words[1] != filename:
                    return None
                groups = []
                output.append({'filename' : filename, 'oldrev' : fromrev, 'newrev' : 'new', 'diff' : groups})
                continue
            # Lines to ignore
            if line.startswith('Index: ') or line.startswith('======'):
                continue
            if groups == None: return None
            # @@ -333,10 +329,8 @@
            if line.startswith('@@ '):
                r = re.match(r'@@ -(\d+),\d+ \+(\d+),\d+ @@', line)
                if not r: return None
                blocks = []
                groups.append(blocks)
                fromline,toline = map(int, r.groups())
                last_type = None
                continue
            if blocks == None: return None

            # 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, 'base.lines': [],
                            'changed.offset': toline,'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'
                        elif len(t) == 0:
                            b['type'] = 'rem'
                        elif len(f) == len(t):
                            _markup_intraline_change(f, t)
                    for i in xrange(len(f)):
                        line = f[i].expandtabs(tabwidth)
                        line = escape(line).replace('\0', '<del>') \
                                           .replace('\1', '</del>')
                        f[i] = space_re.sub(htmlify, line)
                    for i in xrange(len(t)):
                        line = t[i].expandtabs(tabwidth)
                        line = escape(line).replace('\0', '<ins>') \
                                           .replace('\1', '</ins>')
                        t[i] = space_re.sub(htmlify, line)
        return output
    
    except IndexError:
        return None


cs_contents = """
<?cs include "macros.cs"?>

  <style type="text/css">
@import url(<?cs var:htdocs_location ?>/css/diff.css);
  </style>
<div class="diff">
 <ul class="entries">
  <?cs each:item = diff.changes ?>
    <li class="entry">
     <h2><?cs var:item.filename ?></h2>
      <table class="<?cs var:diff.style ?>" summary="Differences" cellspacing="0">
   <?cs if:diff.style == 'sidebyside' ?>
        <colgroup class="base">
         <col class="lineno" /><col class="content" />
        <colgroup class="chg">
         <col class="lineno" /><col class="content" />
        </colgroup>
        <thead><tr>
         <th colspan="2"><?cs var:item.oldrev ?></th>
         <th colspan="2"><?cs var:item.newrev ?></th>
        </tr></thead>
        <?cs each:change = item.diff ?>
         <tbody>
          <?cs call:diff_display(change, diff.style) ?>
         </tbody>
         <?cs if:name(change) < len(item.diff) - 1 ?>
          <tbody class="skippedlines">
           <tr><th>&hellip;</th><td>&nbsp;</td>
           <th>&hellip;</th><td>&nbsp;</td></tr>
          </tbody>
         <?cs /if ?>
        <?cs /each ?>
    <?cs else ?>
        <colgroup>
         <col class="lineno" />
         <col class="lineno" />
         <col class="content" />
        </colgroup>
        <thead><tr>
         <th title="<?cs var:item.oldrev ?>"><?cs var:item.oldrev ?></th>
         <th title="<?cs var:item.newrev ?>"><?cs var:item.newrev ?></th>
         <th>&nbsp;</th>
        </tr></thead>
        <?cs each:change = item.diff ?>
         <?cs call:diff_display(change, diff.style) ?>
         <?cs if:name(change) < len(item.diff) - 1 ?>
          <tbody class="skippedlines">
           <tr><th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td></tr>
          </tbody>
         <?cs /if ?>
        <?cs /each ?><?cs
       /if ?>
      </table>
    </li>
  <?cs /each ?>
 </ul>
</div>
"""

def display(data, mimetype, filename, rev, env):
    from trac.web.clearsilver import HDFWrapper

    res = diff_to_hdf(data.splitlines(), 8)
    if not res: return None
        
    hdf = HDFWrapper(loadpaths=[env.get_templates_dir(),
        env.config.get('trac', 'templates_dir')])
    hdf['diff.style'] = 'inline'
    hdf['diff.changes'] = res
    hdf['htdocs_location'] = env.config.get('trac', 'htdocs_location')
    return hdf.render(hdf.parse(cs_contents))

