### Models

from django.db import models

from docutils.core import publish_parts
from jellyroll.models import Item

def restructuredtext(source):
    """
    Use reStructuredText to format the given source text.
    """
    parts = publish_parts(source=source, writer_name='html4css1')
    return parts['html_body']

class DarcsRepository(models.Model):
    """
    A darcs repository that you may commit patches to.
    """
    name = models.CharField(maxlength=100)
    slug = models.SlugField(prepopulate_from=('name',))
    username = models.CharField(maxlength=100, blank=True, help_text='Your username - if provided, it will be used to filter patches.')
    url = models.URLField(help_text='Don\'t include the <code>_darcs</code> folder.')
    description = models.TextField(blank=True, help_text='Use <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">reStructuredText</a>')
    description_html = models.TextField(editable=False, blank=True)

    class Meta:
        verbose_name_plural = 'darcs repositories'

    class Admin:
        list_display = ('name', 'url', 'username')

    def __unicode__(self):
        return self.name

    def save(self):
        if self.description:
            self.description = self.description.strip()
            self.description_html = restructuredtext(self.description).strip()
        super(DarcsRepository, self).save()

class DarcsPatch(models.Model):
    """
    A darcs patch.
    """
    repository = models.ForeignKey(DarcsRepository, related_name='patches')
    name = models.TextField()
    author = models.CharField(maxlength=150)
    timestamp = models.DateTimeField()
    comment = models.TextField(blank=True)
    inverted = models.BooleanField(default=False)

    class Meta:
        verbose_name_plural ='darcs patches'
        ordering = ['-timestamp']

    class Admin:
        list_display = ('name', 'author', 'timestamp', 'repository')
        list_filter = ('repository',)
        search_fields = ('name', 'comment')

    def __unicode__(self):
        return self.name

# Register item objects to be "followed"
Item.objects.follow_model(DarcsPatch)

### Provider

import datetime
import logging
import re
import time
import urlparse

from django.db import transaction
from django.encoding import smart_unicode

from jellyroll.models import Item
from jellyroll.providers import utils
from somewhere.models import DarcsPatch, DarcsRepository

PATCH_DATE_FORMAT = '%Y%m%d%H%M%S'

patch_pattern = r"""
   \[                                   # Patch start indicator
   (?P<name>[^\n]+)\n                   # Patch name (rest of same line)
   (?P<author>[^\*]+)                   # Patch author
   \*                                   # Author/date separator
   (?P<inverted>[-\*])                  # Inverted patch indicator
   (?P<date>\d{14})                     # Patch date
   (?:\n(?P<comment>(?:^\ [^\n]*\n)+))? # Optional long comment
   \]                                   # Patch end indicator
   """
patch_re = re.compile(patch_pattern, re.VERBOSE | re.MULTILINE)
tidy_comment_re = re.compile(r'^ ', re.MULTILINE)

class Patch:
    """
    Patch details, as defined in a darcs inventory file.

    Attribute names match those generated by the
    ``darcs changes --xml-output`` command.
    """
    def __init__(self, name, author, date, inverted, comment=None):
        self.name = smart_unicode(name)
        self.author = smart_unicode(author)
        self.date = datetime(*time.strptime(date, PATCH_DATE_FORMAT)[:6])
        self.inverted = inverted
        self.comment = smart_unicode(comment)

    def __str__(self):
        return self.name

log = logging.getLogger('newsite.providers.darcs')

#
# Public API
#

def enabled():
    return True # No dependencies

def update():
    for repository in DarcsRepository.objects.all():
        _update_repository(repository)

#
# Private API
#

def _update_repository(repository):
    source_identifier = '%s:%s' % (__name__, repository.url)
    last_update_date = Item.objects.get_last_update_of_model(DarcsPatch, source=source_identifier)
    log.info(u'Updating changes from %s since %s', repository.url, last_update_date)
    inventory = utils.fetch_resource(urlparse.urljoin(repository.url, '_darcs/inventory'))
    for patch in reversed(list(_parse_inventory(inventory))):
        if patch.date < last_update_date:
            log.debug(u'Hit an old patch (commited %s; last update was %s); stopping.', patch.date, last_update_date)
            break
        if repository.username is None or repository.username in patch.author:
            _handle_patch(repository, patch)

def _parse_inventory(inventory):
    """
    Given the contents of a darcs inventory file, generates ``Patch``
    objects representing contained patch details.
    """
    for match in patch_re.finditer(inventory):
        attrs = match.groupdict(None)
        attrs['inverted'] = (attrs['inverted'] == '-')
        if attrs['comment'] is not None:
            attrs['comment'] = tidy_comment_re.sub('', attrs['comment']).strip()
        yield Patch(**attrs)

@transaction.commit_on_success
def _handle_patch(repository, patch):
    log.debug(u'Handling "%s" from %s' % (patch.name, repository.url))
    ci, created = DarcsPatch.objects.get_or_create(
        repository = repository,
        author     = patch.author,
        name       = patch.name,
        timestamp  = patch.date,
        inverted   = patch.inverted,
        defaults   = {'comment': patch.comment}
    )
    return Item.objects.create_or_update(
        instance  = ci,
        timestamp = patch.date,
        source    = u'%s:%s' % (__name__, repository.url),
    )
