### 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 _darcs folder.') description = models.TextField(blank=True, help_text='Use reStructuredText') 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[^\n]+)\n # Patch name (rest of same line) (?P[^\*]+) # Patch author \* # Author/date separator (?P[-\*]) # Inverted patch indicator (?P\d{14}) # Patch date (?:\n(?P(?:^\ [^\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), )