### 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),
)