#! /usr/bin/env python ######################################################################## # COPYRIGHT_BEGIN # # mical # Minimal iCalendar utilities # # Copyright (C) 2007, David Arnold # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2, as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # COPYRIGHT_END ######################################################################## # $ZeroXOne: mical/micshow,v 1.9 2007/06/04 17:13:05 d Exp $ ######################################################################## import sys, os, vobject from optparse import OptionParser from dateutil.tz import tzlocal from mical import VERSION PROGRAM = os.path.basename(sys.argv[0]) INTERVALS = { 1: "", 2: "second", 3: "third", 4: "fourth", 5: "fifth", 6: "sixth", 7: "seventh", 8: "eighth", 9: "nineth", 10: "tenth", 11: "11th", 12: "12th", 13: "13th" } ######################################################################## def main(): parser = OptionParser(description = "Write a user-friendly summary " "representation of an iCalendar object to " "the standard output device.\n\nThe iCalendar " "object is read from the specified saved event, " "or from the standard input device.", usage="%prog [index]", version=PROGRAM + "-" + VERSION) (options, args) = parser.parse_args() # Check for saved event name if len(args) == 1: path = "%s/.mical/%s" % (os.environ["HOME"], sys.argv[1]) if not os.path.exists(path): path = sys.argv[1] try: f = open(path) except IOError: print "No such file or saved calendar item: %s" % sys.argv[1] sys.exit(1) vobj = vobject.readOne(f) f.close else: vobj = vobject.readOne(sys.stdin) # Print more-or-less nicely event = vobj.vevent # Action if hasattr(vobj, 'method'): method = vobj.method.value.capitalize() print "Meeting " + method print "--------" + ("-" * len(method)) # Summary if hasattr(event, 'summary'): print "Summary\n\t" + event.summary.value + "\n" # Location if hasattr(event, 'location'): print "Location\n\t" + event.location.value + "\n" # Time if hasattr(event, 'dtstart'): start = event.dtstart.value local_start = start.astimezone(tzlocal()) if hasattr(event, 'dtend'): end = event.dtend.value local_end = end.astimezone(tzlocal()) elif hasattr(event, 'duration'): end = start + event.duration.value local_end = end.astimezone(tzlocal()) s = "Time\n" s += "\t" + local_start.strftime("%l:%M %p").strip() s += " - " + local_end.strftime("%l:%M %p %Z").strip() print s #FIXME: repeat rule if hasattr(event, 'rrule'): rrules = {} for param in event.rrule.value.split(";"): k,v = param.split("=") rrules[k.upper()] = v.upper() if rrules.has_key('FREQ'): freq = rrules['FREQ'].lower()[:-2] else: freq = "" if rrules.has_key('INTERVAL'): interval_num = int(rrules['INTERVAL']) if interval_num < 14: interval = INTERVALS[interval_num] elif interval_num % 10 == 1: interval = str(interval_num) + "st" elif interval_num % 10 == 2: interval = str(interval_num) + "nd" elif interval_num % 10 == 3: interval = str(interval_num) + "rd" else: interval = str(interval_num) + "th" else: interval = "" print "\tEvery %s%s%s" % (interval, interval and " ", freq) s = "\tStarting " else: s = "\t" s += local_start.strftime("%A, %d %B %Y") + "\n" print s # Attendees if event.contents.has_key('attendee'): print "Attendees" for attendee in event.contents['attendee']: email = attendee.value.split(':')[1] s = "\t" if attendee.params.has_key('CN'): s += attendee.params['CN'][0] + " <" + email + ">" else: s += email if attendee.params.has_key('ROLE'): s += " (" for role in attendee.params['ROLE']: if role == 'REQ-PARTICIPANT': s += "Required" elif role == 'OPT-PARTICIPANT': s += "Optional" elif role == 'NON-PARTICIPANT': s += "Informational copy" else: s += role.capitalize() # CHAIR + other s += ")" if attendee.params.has_key('PARTSTAT'): s += ": " for status in attendee.params['PARTSTAT']: s += status.capitalize().replace("-", " ") + " " print s if hasattr(event, 'organizer'): organizer = event.organizer email = organizer.value.split(":")[1] s = "\n\t" if organizer.params.has_key('CN'): for cn in organizer.params['CN']: s += cn + " " s += "<" + email + ">" else: s += email s += " (Organizer)" print s print # Description if hasattr(event, 'description'): desc = event.description.value # Remove Outlook-specific description prefix if present outlook_nonsense = desc.find("*~*~*~*~*~*~*~*~*~*") if outlook_nonsense > 0: desc = desc[outlook_nonsense+19:].strip() if len(desc) > 0: desc = desc.encode("utf-8", 'replace') desc = desc.replace('\r', '') desc = reduce(lambda line, word: '%s%s%s' % (line, ' \n'[(len(line) - line.rfind('\n') - 1 + len(word.split('\n', 1)[0]) >= 60)], word), desc.split(' ')) desc = desc.replace("\n", "\n\t") print "Description\n\t" + desc if __name__ == "__main__": main() ########################################################################