diff --git a/test-requirements.txt b/test-requirements.txt index 6578a34..072380f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,4 @@ -hacking>=0.11.0,<0.12 # Apache-2.0 +hacking>=3.2.0 # Apache-2.0 coverage>=3.6 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD diff --git a/yaml2ical/ical.py b/yaml2ical/ical.py index 7968e12..cd8d1da 100644 --- a/yaml2ical/ical.py +++ b/yaml2ical/ical.py @@ -38,92 +38,92 @@ class Yaml2IcalCalendar(icalendar.Calendar): self.add_schedule(meeting, sch) def add_schedule(self, meeting, sch, exdate=None): - event = icalendar.Event() + event = icalendar.Event() - # NOTE(jotan): I think the summary field needs to be unique per - # event in an ical file (at least, for it to work with - # Google Calendar) + # NOTE(jotan): I think the summary field needs to be unique per + # event in an ical file (at least, for it to work with + # Google Calendar) - summary = meeting.project - # NOTE(tonyb): If we're adding an a place holder event for a - # cancelled meeting make that as obvious as possible in the - # summary. - if exdate: - # NOTE(tonyb): Because some iCal consumers require that the - # summary be unique, and adding multiple "CANCELLED: $x" - # entries would violate that rule, append the (UTC) - # timestamp for the cancelled meeting. - suffix = exdate.date_str - summary = 'CANCELLED: %s (%s)' % (summary, suffix) - event.add('summary', summary) - event.add('location', '#' + sch.irc) + summary = meeting.project + # NOTE(tonyb): If we're adding an a place holder event for a + # cancelled meeting make that as obvious as possible in the + # summary. + if exdate: + # NOTE(tonyb): Because some iCal consumers require that the + # summary be unique, and adding multiple "CANCELLED: $x" + # entries would violate that rule, append the (UTC) + # timestamp for the cancelled meeting. + suffix = exdate.date_str + summary = 'CANCELLED: %s (%s)' % (summary, suffix) + event.add('summary', summary) + event.add('location', '#' + sch.irc) - # add ical description - project_descript = "Project: %s" % (meeting.project) - chair_descript = "Chair: %s" % (meeting.chair) - descript_descript = "Description: %s" % (meeting.description) - ical_descript = "\n".join((project_descript, - chair_descript, - descript_descript)) - # Add URLs, if present, to the description - if 'agenda_url' in meeting.extras: - ical_descript = "\n".join((ical_descript, - "Agenda URL: %s" % - (meeting.extras['agenda_url']))) - if 'project_url' in meeting.extras: - ical_descript = "\n".join((ical_descript, - "Project URL: %s" % - (meeting.extras['project_url']))) + # add ical description + project_descript = "Project: %s" % (meeting.project) + chair_descript = "Chair: %s" % (meeting.chair) + descript_descript = "Description: %s" % (meeting.description) + ical_descript = "\n".join((project_descript, + chair_descript, + descript_descript)) + # Add URLs, if present, to the description + if 'agenda_url' in meeting.extras: + ical_descript = "\n".join((ical_descript, + "Agenda URL: %s" % + (meeting.extras['agenda_url']))) + if 'project_url' in meeting.extras: + ical_descript = "\n".join((ical_descript, + "Project URL: %s" % + (meeting.extras['project_url']))) - # NOTE(tonyb): If we're adding an a place holder event for a - # cancelled meeting do not add an rrule and set dtstart to the - # skipped date. - if not exdate: - # get starting date - next_meeting = sch.recurrence.next_occurence(sch.start_date, - sch.day) - # NOTE(aschultz): to handle adhoc meetings, we check to make - # sure there is a next meeting before trying to add it to the - # the calendar - if next_meeting is None: - return - next_meeting_date = datetime.datetime(next_meeting.year, - next_meeting.month, - next_meeting.day, - sch.time.hour, - sch.time.minute, - tzinfo=pytz.utc) - event.add('dtstart', next_meeting_date) + # NOTE(tonyb): If we're adding an a place holder event for a + # cancelled meeting do not add an rrule and set dtstart to the + # skipped date. + if not exdate: + # get starting date + next_meeting = sch.recurrence.next_occurence(sch.start_date, + sch.day) + # NOTE(aschultz): to handle adhoc meetings, we check to make + # sure there is a next meeting before trying to add it to the + # the calendar + if next_meeting is None: + return + next_meeting_date = datetime.datetime(next_meeting.year, + next_meeting.month, + next_meeting.day, + sch.time.hour, + sch.time.minute, + tzinfo=pytz.utc) + event.add('dtstart', next_meeting_date) - # add recurrence rule - event.add('rrule', sch.recurrence.rrule()) - event.add('description', ical_descript) - else: - event.add('dtstart', exdate.date) - event.add('description', exdate.reason) + # add recurrence rule + event.add('rrule', sch.recurrence.rrule()) + event.add('description', ical_descript) + else: + event.add('dtstart', exdate.date) + event.add('description', exdate.reason) - event.add('duration', datetime.timedelta(minutes=sch.duration)) + event.add('duration', datetime.timedelta(minutes=sch.duration)) - # Add exdate (exclude date) if present - if not exdate and hasattr(sch, 'skip_dates'): - for skip_date in sch.skip_dates: - event.add('exdate', skip_date.date) - # NOTE(tonyb): If this is a skipped meeting add a - # non-recurring event with an obvious summary. - self.add_schedule(meeting, sch, exdate=skip_date) + # Add exdate (exclude date) if present + if not exdate and hasattr(sch, 'skip_dates'): + for skip_date in sch.skip_dates: + event.add('exdate', skip_date.date) + # NOTE(tonyb): If this is a skipped meeting add a + # non-recurring event with an obvious summary. + self.add_schedule(meeting, sch, exdate=skip_date) - # Add update timestamp and unique ID for the event. - # Note: we need a way to uniquely identify the meeting, even if its - # details are modified. We don't really have much to go on to do - # that, so best effort just use the project name and date. - event.add('dtstamp', meeting.last_update) - start_date = exdate.date if exdate else next_meeting_date - event.add('uid', '%s-%s' % ( - meeting.project.replace(' ', '').lower(), - datetime.datetime.strftime(start_date, '%Y%m%d'))) + # Add update timestamp and unique ID for the event. + # Note: we need a way to uniquely identify the meeting, even if its + # details are modified. We don't really have much to go on to do + # that, so best effort just use the project name and date. + event.add('dtstamp', meeting.last_update) + start_date = exdate.date if exdate else next_meeting_date + event.add('uid', '%s-%s' % ( + meeting.project.replace(' ', '').lower(), + datetime.datetime.strftime(start_date, '%Y%m%d'))) - # add event to calendar - self.add_component(event) + # add event to calendar + self.add_component(event) def write_to_disk(self, filename): # write ical files to disk diff --git a/yaml2ical/tests/test_meeting.py b/yaml2ical/tests/test_meeting.py index 689eec9..0314d04 100644 --- a/yaml2ical/tests/test_meeting.py +++ b/yaml2ical/tests/test_meeting.py @@ -183,15 +183,15 @@ class MeetingTestCase(unittest.TestCase): summary = 'OpenStack Subteam 8 Meeting' patterns = [] # The "main" meeting should have an exdate - patterns.append(re.compile('.*exdate:\s*20150810T120000', re.I)) + patterns.append(re.compile(r'.*exdate:\s*20150810T120000', re.I)) # The "main" meeting should start on 2015-08-13 - patterns.append(re.compile('.*dtstart;.*:20150803T120000Z', re.I)) + patterns.append(re.compile(r'.*dtstart;.*:20150803T120000Z', re.I)) # The "main" meeting should have a simple summary - patterns.append(re.compile('.*summary:\s*%s' % summary, re.I)) + patterns.append(re.compile(r'.*summary:\s*%s' % summary, re.I)) # The "skipped" meeting should start on 20150806 - patterns.append(re.compile('.*dtstart;.*:20150810T120000Z', re.I)) + patterns.append(re.compile(r'.*dtstart;.*:20150810T120000Z', re.I)) # The "skipped" meeting should say include 'CANCELLED' and the datetime - patterns.append(re.compile('.*summary:\s*CANCELLED.*20150810T120000Z', + patterns.append(re.compile(r'.*summary:\s*CANCELLED.*20150810T120000Z', re.I)) m = meeting.load_meetings(meeting_yaml)[0] cal = ical.Yaml2IcalCalendar()