#! /usr/bin/env python # # Copyright 2014 North Dakota State University # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import datetime import icalendar import logging import os import pytz import yaml import const class Meeting: """An OpenStack meeting.""" def __init__(self, yaml, filename): """Initialize meeting from yaml file name 'filename'.""" self.filename = filename # initialize using yaml self.project = yaml['project'] self.chair = yaml['chair'] self.description = yaml['description'] self.agenda = yaml['agenda'] # this is a list of list of topics # create schedule objects self.schedules = [Schedule(schedule) for schedule in yaml['schedule']] def write_ical(self): """Write this meeting to disk using the iCal format.""" cal = icalendar.Calendar() # add properties to ensure compliance cal.add('prodid', '-//OpenStack//Gerrit-Powered Meeting Agendas//EN') cal.add('version', '2.0') i = 1 for schedule in self.schedules: # one Event per iCal file 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) event.add('summary', self.project + ' ' + str(i)) # add ical description project_descript = "Project: %s" % (self.project) chair_descript = "Chair: %s" % (self.chair) irc_descript = "IRC: %s" % (schedule.irc) agenda_yaml = yaml.dump(self.agenda, default_flow_style=False) agenda_descript = "Agenda:\n%s\n" % (agenda_yaml) descript_descript = "Description: %s" % (self.description) ical_descript = "\n".join((project_descript, chair_descript, irc_descript, agenda_descript, descript_descript)) event.add('description', ical_descript) # get starting date d = datetime.datetime.utcnow() next_meeting = next_weekday(d, const.WEEKDAYS[schedule.day]) next_meeting_dt = datetime.datetime(next_meeting.year, next_meeting.month, next_meeting.day, schedule.time.hour, schedule.time.minute, tzinfo=pytz.utc) event.add('dtstart', next_meeting_dt) # add recurrence rule event.add('rrule', {'freq': schedule.freq}) # add meeting length # TODO(jotan): determine duration to use for OpenStack meetings event.add('duration', datetime.timedelta(hours=1)) # add event to calendar cal.add_component(event) i += 1 # write ical files to disk ical_dir = '../icals' ical_filename = self.filename[:-4] + 'ics' if not os.path.exists(ical_dir): os.makedirs(ical_dir) os.chdir(ical_dir) with open(ical_filename, 'wb') as ics: ics.write(cal.to_ical()) num_events = len(cal.subcomponents) logging.info('\'%s\' processed. [%d event(s)]' % (ical_filename, num_events)) class Schedule: """A meeting schedule.""" def __init__(self, sched_yaml): """Initialize schedule from yaml.""" self.time = datetime.datetime.strptime(sched_yaml['time'], '%H%M') self.day = sched_yaml['day'] self.irc = sched_yaml['irc'] self.freq = sched_yaml['frequency'] def next_weekday(ref_date, weekday): """Return the date of the next weekday after ref_date.""" days_ahead = weekday - ref_date.weekday() if days_ahead <= 0: # target day already happened this week days_ahead += 7 return ref_date + datetime.timedelta(days_ahead)