Attachment 'monthcal2ics.py'

Download

   1 # -*- coding: iso-8859-1 -*-
   2 """
   3     MoinMoin - Get an iCalendar (ics) feed from a MonthCalendar
   4     
   5     @copyright: 2010 Florian Birée <florian@biree.name>
   6     @license: GNU GPL, see COPYING for details.
   7     @version: 0.2
   8     
   9     Version 0.2:
  10         * add the support of MoinMoin 1.8, add the mime type in result headers
  11     Version 0.1:
  12         * first version, for MoinMoin 1.9
  13 """
  14 
  15 import calendar
  16 import datetime
  17 import re
  18 
  19 from MoinMoin.Page import Page
  20 
  21 # Some constants :
  22 #   Range of time (relative from today) where events are searched:
  23 START_RANGE = datetime.timedelta(days=31)   # before today
  24 END_RANGE = datetime.timedelta(days=6*31)   # after today
  25 #   Default duration of an event:
  26 DEFAULT_DURATION = datetime.timedelta(hours=2)
  27 
  28 class VCalendar(list):
  29     """Far uncomplete implementation of vCalendar"""
  30     
  31     def __unicode__(self):
  32         """Return the calendar in the iCalendar format"""
  33         return (
  34             u"BEGIN:VCALENDAR\nVERSION:2.0\n" +
  35             u"PRODID:-//MoinMoin MonthCalendar2ics\n" +
  36             u"".join([unicode(event) for event in self]) + 
  37             u"END:VCALENDAR\n"
  38         )
  39     
  40     def __str__(self):
  41         """Return the calendar in the iCalendar format (utf-8 encoded)"""
  42         return unicode(self).encode('utf-8')
  43 
  44 class VEvent(object):
  45     """Far uncomplete implementation of vEvent"""
  46     
  47     def __init__(self, summary, dtstart, dtend=None, description=None):
  48         """Create a new event
  49         
  50         summary is a short description of the event
  51         dtstart is a datetime.date for a full-day event, or a datetime.datetime
  52         dtend is a datetime.datetime
  53         description is the long description of the event
  54         """
  55         self.summary = summary
  56         self.dtstart = dtstart
  57         self.dtend = dtend
  58         self.description = description
  59     
  60     def __unicode__(self):
  61         """Return the event in the iCalendar format"""
  62         def idate(date):
  63             """Return the date or datetime in the iCalendar format"""
  64             return unicode(date.isoformat().replace(':', u'').replace('-', u''))
  65         def istr(raw_str):
  66             """Return the string in with iCalendar escaped new lines"""
  67             return unicode(raw_str.replace('\n', '\\n'))
  68         
  69         ev_str = u"BEGIN:VEVENT\n"
  70         ev_str += u"DTSTART:%s\n" % idate(self.dtstart)
  71         if self.dtend:
  72             ev_str += u"DTEND:%s\n" % idate(self.dtend)
  73         ev_str += u"SUMMARY:%s\n" % istr(self.summary)
  74         if self.description:
  75             ev_str += u"DESCRIPTION:%s\n" % istr(self.description)
  76         ev_str += u"END:VEVENT\n"
  77         return ev_str
  78     
  79     def __str__(self):
  80         """Return the event in the iCalendar format (utf-8 encoded)"""
  81         return unicode(self).encode('utf-8')
  82 
  83 def extract_events_from_page(content, year, month, day):
  84     """Parse a MoinMoin page and extract events. Return a VCalendar object."""
  85     def tatoi(time_tupple):
  86         """Convert a string time tupple to an int tupple"""
  87         if time_tupple[0]:
  88             h = int(time_tupple[0])
  89         else:
  90             h = 0
  91         if time_tupple[1]:
  92             m = int(time_tupple[1])
  93         else:
  94             m = 0
  95         return (h, m)
  96     
  97     level1headers = re.compile(r'^\s*=\s(.*)\s=$', re.MULTILINE)
  98     times = re.compile(r'(\d{1,2})\s*[hH:](?:\s*(\d{2}))?')
  99     
 100     splitted_page = level1headers.split(content)
 101     cal = VCalendar()
 102     for i in range(len(splitted_page) / 2):
 103         summary = splitted_page[i*2+1]
 104         description = splitted_page[(i+1) * 2].strip()
 105         found_times = times.findall(summary)
 106         if len(found_times) == 0: # no start time, full day event
 107             start_date = datetime.date(year, month, day)
 108             end_date = None
 109         elif len(found_times) == 1: # one start time, 1h event
 110             h, m = tatoi(found_times[0])
 111             start_date = datetime.datetime(year, month, day, h, m)
 112             duration = DEFAULT_DURATION
 113             end_date = start_date + duration
 114         else: # at least two times (start and end of the event)
 115             h, m = tatoi(found_times[0])
 116             start_date = datetime.datetime(year, month, day, h, m)
 117             h, m = tatoi(found_times[1])
 118             end_date = datetime.datetime(year, month, day, h, m)
 119             if not start_date < end_date:
 120                 duration = DEFAULT_DURATION
 121                 end_date = start_date + duration
 122         cal.append(VEvent(summary, start_date, end_date, description))
 123     return cal
 124 
 125 def execute(pagename, request):
 126     """Execute the generation of the ics feed.
 127     
 128     Pagename must be the page where the month calendar is.
 129     """
 130     start_date = datetime.date.today() - START_RANGE
 131     end_date = datetime.date.today() + END_RANGE
 132     year_month_list = []
 133     current_date = start_date.replace(day=1)
 134     while current_date < end_date:
 135         year_month_list.append((current_date.year, current_date.month))
 136         firstday, monthlen = calendar.monthrange(current_date.year,
 137                                                  current_date.month)
 138         current_date += datetime.timedelta(days=monthlen)
 139     
 140     # Search for events and build the calendar
 141     cal = VCalendar()
 142     for year, month in year_month_list:
 143         firstday, monthlen = calendar.monthrange(year, month)
 144         for day in range(1, monthlen + 1):
 145             link = "%s/%4d-%02d-%02d" % (pagename, year, month, day)
 146             daypage = Page(request, link)
 147             if daypage.exists() and request.user.may.read(link):
 148                 #print "found :", link
 149                 daycontent = daypage.get_raw_body()
 150                 cal += extract_events_from_page(daycontent, year,month, day)
 151     # Send the iCalendar file
 152     request.content_type = 'text/calendar'
 153     if hasattr(request, 'headers') and hasattr(request.headers, 'add'):#moin 1.9
 154         request.headers.add('Content-Type: text/calendar')
 155         request.headers.add('Content-Disposition', 'inline; filename="%s.ics"' %
 156         pagename)
 157     else: # moin 1.8
 158         request.emit_http_headers([                                                  
 159             'Content-Type: text/calendar',
 160             'Content-Disposition: inline; filename="%s.ics"' % pagename,
 161         ])
 162     
 163     request.write(str(cal))
 164     #request.close()
 165 
 166                     
 167                     
 168                     
 169                     

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2010-02-05 17:41:09, 6.2 KB) [[attachment:monthcal2ics.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.