From 71e1e60157176f81ee9a40d9e43d4dbd90c636ef Mon Sep 17 00:00:00 2001 From: Dustin Swan Date: Wed, 20 Mar 2013 17:40:21 -0500 Subject: [PATCH] Removing the old gcalcli python script --- bin/gcalcli | 1581 --------------------------------------------------- 1 file changed, 1581 deletions(-) delete mode 100755 bin/gcalcli diff --git a/bin/gcalcli b/bin/gcalcli deleted file mode 100755 index c78975e..0000000 --- a/bin/gcalcli +++ /dev/null @@ -1,1581 +0,0 @@ -#!/usr/bin/env python - -# ** The MIT License ** -# -# Copyright (c) 2007 Eric Davis (aka Insanum) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# Dude... just buy me a beer. :-) -# - -# -# Home: http://code.google.com/p/gcalcli -# -# Author: Eric Davis -# -# Requirements: -# - Python 2 -# http://www.python.org -# - Google's GData Python module (for Python 2) -# http://code.google.com/p/gdata-python-client -# - dateutil Python module -# http://www.labix.org/python-dateutil -# - vobject Python module (optional, needed for importing ics/vcal files) -# http://vobject.skyhouseconsulting.com -# - -__program__ = 'gcalcli' -__version__ = 'v2.2' -__author__ = 'Eric Davis' - -import inspect - -import sys, os, re, urllib, getopt, shlex, subprocess -import codecs, locale, csv, threading, getpass -from Queue import Queue -from ConfigParser import RawConfigParser -from gdata.calendar.service import * -from datetime import * -from dateutil.tz import * -from dateutil.parser import * -from dateutil.rrule import * -from unicodedata import east_asian_width - - -def Version(): - sys.stdout.write(__program__+' '+__version__+' ('+__author__+')\n') - sys.exit(1) - - -def Usage(): - sys.stdout.write(''' -Usage: - -gcalcli [options] command [command args] - - Options: - - --help this usage text - - --version version information - - --config config file to read (default is '~/.gcalclirc') - - --user google username - - --pw password - - --cals [all, 'calendars' to work with (default is all calendars) - default, - default (your default main calendar) - owner, - owner (your owned calendars) - editor, - editor (editable calendar) - contributor, - contributor (non-owner but able to edit) - read, - read (read only calendars) - freebusy] - freebusy (only free/busy info visible) - - --cal [#color] 'calendar' to work with (default is all calendars) - - you can specify a calendar by name or by substring - which can match multiple calendars - - you can use multiple '--cal' arguments on the - command line - - in the config file specify multiple calendars in - quotes separated by commas as: - cal: "foo", "bar", "my cal" - - an optional color override can be specified per - calendar using the ending hashtag: - --cal "Eric Davis"#green --cal foo#red - or via the config file: - cal: "foo"#red, "bar"#yellow, "my cal"#green - - --24hr show all dates in 24 hour format - - --details show all event details (i.e. length, location, - reminders, contents) - - --ignore-started ignore old or already started events - - when used with the 'agenda' command, ignore events - that have already started and are in-progress with - respect to the specified [start] time - - when used with the 'search' command, ignore events - that have already occurred and only show future - events - - --width the number of characters to use for each column in - the 'calw' and 'calm' command outputs (default is 10) - - --mon week begins with Monday for 'calw' and 'calm' command - outputs (default is Sunday) - - --nc don't use colors - - --cal-owner-color specify the colors used for the calendars and dates - --cal-editor-color each of these argument requires a argument - --cal-contributor-color which must be one of [ default, black, brightblack, - --cal-read-color red, brightred, green, brightgreen, yellow, - --cal-freebusy-color brightyellow, blue, brightblue, magenta, - --date-color brightmagenta, cyan, brightcyan, white, - --border-color brightwhite ] - - --tsv tab-separated output for 'agenda'. Format is: - 'date' 'start' 'end' 'title' 'location' 'description' - - Commands: - - list list all calendars - - search search for events - - only matches whole words - - agenda [start] [end] get an agenda for a time period - - start time default is 12am today - - end time default is 5 days from start - - example time strings: - '9/24/2007' - 'Sep 24 2007 3:30pm' - '2007-09-24T15:30' - '2007-09-24T15:30-8:00' - '20070924T15' - '8am' - - calw [start] get a week based agenda in a nice calendar format - - weeks is the number of weeks to display - - start time default is beginning of this week - - note that all events for the week(s) are displayed - - calm [start] get a month agenda in a nice calendar format - - start time default is the beginning of this month - - note that all events for the month are displayed - and only one month will be displayed - - quick quick add an event to a calendar - - if a --cal is not specified then the event is - added to the default calendar - - example: - 'Dinner with Eric 7pm tomorrow' - '5pm 10/31 Trick or Treat' - - import [-v] [file] import an ics/vcal file to a calendar - - if a --cal is not specified then the event is - added to the default calendar - - if a file is not specified then the data is read - from standard input - - if -v is given then each event in the file is - displayed and you're given the option to import - or skip it, by default everything is imported - quietly without any interaction - - remind execute command if event occurs within - minutes time ('%s' in is replaced with - event start time and title text) - - default is 10 - - default command: - 'notify-send -u critical -a gcalcli %s' -''') - sys.exit(1) - - -class CLR: - - useColor = True - - def __str__(self): - if self.useColor: return self.color - else: return "" - -class CLR_NRM(CLR): color = "\033[0m" -class CLR_BLK(CLR): color = "\033[0;30m" -class CLR_BRBLK(CLR): color = "\033[30;1m" -class CLR_RED(CLR): color = "\033[0;31m" -class CLR_BRRED(CLR): color = "\033[31;1m" -class CLR_GRN(CLR): color = "\033[0;32m" -class CLR_BRGRN(CLR): color = "\033[32;1m" -class CLR_YLW(CLR): color = "\033[0;33m" -class CLR_BRYLW(CLR): color = "\033[33;1m" -class CLR_BLU(CLR): color = "\033[0;34m" -class CLR_BRBLU(CLR): color = "\033[34;1m" -class CLR_MAG(CLR): color = "\033[0;35m" -class CLR_BRMAG(CLR): color = "\033[35;1m" -class CLR_CYN(CLR): color = "\033[0;36m" -class CLR_BRCYN(CLR): color = "\033[36;1m" -class CLR_WHT(CLR): color = "\033[0;37m" -class CLR_BRWHT(CLR): color = "\033[37;1m" - - -def PrintErrMsg(msg): - if CLR.useColor: - sys.stdout.write(str(CLR_BRRED())) - sys.stdout.write(msg) - sys.stdout.write(str(CLR_NRM())) - else: - sys.stdout.write(msg) - - -def PrintMsg(color, msg): - if CLR.useColor: - sys.stdout.write(str(color)) - sys.stdout.write(msg) - sys.stdout.write(str(CLR_NRM())) - else: - sys.stdout.write(msg) - - -def DebugPrint(msg): - return - sys.stdout.write(str(CLR_YLW())) - sys.stdout.write(msg) - sys.stdout.write(str(CLR_NRM())) - - -class gcalcli: - - gcal = None - allCals = None - cals = [] - now = datetime.now(tzlocal()) - feedPrefix = 'https://www.google.com/calendar/feeds/' - agendaLength = 5 - username = None - password = None - access = '' - military = False - details = False - ignoreStarted = False - calWidth = 10 - calMonday = False - command = 'notify-send -u critical -a gcalcli %s' - tsv = False - - calOwnerColor = CLR_CYN() - calEditorColor = CLR_NRM() - calContributorColor = CLR_NRM() - calReadColor = CLR_MAG() - calFreeBusyColor = CLR_NRM() - dateColor = CLR_YLW() - borderColor = CLR_WHT() - - ACCESS_ALL = 'all' # non-google access level - ACCESS_DEFAULT = 'default' # non-google access level - ACCESS_CONTRIBUTOR = 'contributor' - ACCESS_EDITOR = 'editor' - ACCESS_FREEBUSY = 'freebusy' - ACCESS_NONE = 'none' - ACCESS_OVERRIDE = 'override' - ACCESS_OWNER = 'owner' - ACCESS_READ = 'read' - ACCESS_RESPOND = 'respond' - ACCESS_ROOT = 'root' - - - def __init__(self, - username=None, - password=None, - access='all', - calNames=[], - calNameColors=[], - military=False, - details=False, - ignoreStarted=False, - calWidth=10, - calMonday=False, - calOwnerColor=CLR_CYN(), - calEditorColor=CLR_GRN(), - calContributorColor=CLR_NRM(), - calReadColor=CLR_MAG(), - calFreeBusyColor=CLR_NRM(), - dateColor=CLR_GRN(), - borderColor=CLR_WHT(), - tsv=False): - - self.gcal = CalendarService() - self.gcal.ssl = True - - self.username = username - self.password = password - - self.access = access - self.military = military - self.details = details - self.ignoreStarted = ignoreStarted - self.calWidth = calWidth - self.calMonday = calMonday - self.tsv = tsv - - self.calOwnerColor = calOwnerColor - self.calEditorColor = calEditorColor - self.calContributorColor = calContributorColor - self.calReadColor = calReadColor - self.calFreeBusyColor = calFreeBusyColor - self.dateColor = dateColor - self.borderColor = borderColor - - # authenticate and login to google calendar - try: - self.gcal.ClientLogin( - username=self.username, - password=self.password, - service='cl', - source=__author__+'-'+__program__+'-'+__version__) - except Exception, e: - PrintErrMsg("Error: " + str(e) + "!\n") - sys.exit(1) - - # get the list of calendars - self.allCals = self.gcal.GetAllCalendarsFeed() - - # gcalcli defined way to order calendars XXX - order = { self.ACCESS_OWNER : 1, - self.ACCESS_EDITOR : 2, - self.ACCESS_ROOT : 3, - self.ACCESS_CONTRIBUTOR : 4, - self.ACCESS_OVERRIDE : 5, - self.ACCESS_RESPOND : 6, - self.ACCESS_FREEBUSY : 7, - self.ACCESS_READ : 8, - self.ACCESS_NONE : 9 } - - self.allCals.entry.sort(lambda x, y: - cmp(order[x.access_level.value], - order[y.access_level.value])) - - for cal in self.allCals.entry: - - cal.gcalcli_altLink = cal.GetAlternateLink().href - - match = re.match('^' + self.feedPrefix + '(.*?)/(.*?)/(.*)$', - cal.gcalcli_altLink) - cal.gcalcli_username = urllib.unquote(match.group(1)) - cal.gcalcli_visibility = urllib.unquote(match.group(2)) - cal.gcalcli_projection = urllib.unquote(match.group(3)) - - if len(calNames): - for i in xrange(len(calNames)): - if re.search(calNames[i].lower(), - cal.title.text.lower()): - self.cals.append(cal) - cal.colorSpec = calNameColors[i] - else: - self.cals.append(cal) - cal.colorSpec = None - - - def _CalendarWithinAccess(self, cal): - - if self.access == self.ACCESS_ALL: - return True - elif self.access == self.ACCESS_DEFAULT and \ - cal.gcalcli_username == self.username: - return True - elif self.access != cal.access_level.value: - return False - else: - return True - - - def _CalendarColor(self, cal): - - if cal == None: - return CLR_NRM() - elif hasattr(cal, 'colorSpec') and cal.colorSpec != None: - return cal.colorSpec - elif cal.access_level.value == self.ACCESS_OWNER: - return self.calOwnerColor - elif cal.access_level.value == self.ACCESS_EDITOR: - return self.calEditorColor - elif cal.access_level.value == self.ACCESS_CONTRIBUTOR: - return self.calContributorColor - elif cal.access_level.value == self.ACCESS_FREEBUSY: - return self.calFreeBusyColor - elif cal.access_level.value == self.ACCESS_READ: - return self.calReadColor - else: - return CLR_NRM() - - - def _TargetCalendar(self): - - if len(self.cals) == 1: - match = re.match('^https://www.google.com(.*)$', - self.cals[0].gcalcli_altLink) - return match.group(1) - else: - return '/calendar/feeds/default/private/full' - - - def _ValidTitle(self, title): - if title == None: - return "(No title)" - else: - return title - - - def _GetWeekEventStrings(self, cmd, curMonth, - startDateTime, endDateTime, eventList): - - weekEventStrings = [ '', '', '', '', '', '', '' ] - - for event in eventList: - - if cmd == 'calm' and curMonth != event.s.strftime("%b"): - continue - - dayNum = int(event.s.strftime("%w")) - if self.calMonday: - dayNum -= 1 - if dayNum < 0: - dayNum = 6 - - if event.s >= startDateTime and event.s < endDateTime: - - if event.s.hour == 0 and event.s.minute == 0 and \ - event.e.hour == 0 and event.e.minute == 0: - tmpTimeStr = '' - elif self.military: - tmpTimeStr = event.s.strftime("%H:%M") - else: - tmpTimeStr = \ - event.s.strftime("%I:%M").lstrip('0') + \ - event.s.strftime('%p').lower() - - # newline and empty string are the keys to turn off coloring - weekEventStrings[dayNum] += \ - "\n" + \ - str(self._CalendarColor(event.gcalcli_cal)) + \ - tmpTimeStr.strip() + \ - " " + \ - self._ValidTitle(event.title.text).strip() - - return weekEventStrings - - - UNIWIDTH = {'W': 2, 'F': 2, 'N': 1, 'Na': 1, 'H': 1, 'A': 1} - - - def _PrintLen(self, string): - printLen = 0 - for tmpChar in string: - printLen += self.UNIWIDTH[east_asian_width(tmpChar)] - return printLen - - - # return print length before cut, cut index, and force cut flag - def _NextCut(self, string, curPrintLen): - idx = 0 - printLen = 0 - for tmpChar in string: - if (curPrintLen + printLen) >= self.calWidth: - return (printLen, idx, True) - if tmpChar in (' ', '\n'): - return (printLen, idx, False) - idx += 1 - printLen += self.UNIWIDTH[east_asian_width(tmpChar)] - return (printLen, -1, False) - - - def _GetCutIndex(self, eventString): - - printLen = self._PrintLen(eventString) - - if printLen <= self.calWidth: - DebugPrint("------ printLen=%d (end of string)\n" % printLen) - return (printLen, len(eventString)) - - cutWidth, cut, forceCut = self._NextCut(eventString, 0) - DebugPrint("------ cutWidth=%d cut=%d \"%s\"\n" % - (cutWidth, cut, eventString)) - - if forceCut: - DebugPrint("--- forceCut cutWidth=%d cut=%d\n" % (cutWidth, cut)) - return (cutWidth, cut) - - DebugPrint("--- looping\n") - - while cutWidth < self.calWidth: - - DebugPrint("--- cutWidth=%d cut=%d \"%s\"\n" % - (cutWidth, cut, eventString[cut:])) - - while cut < self.calWidth and \ - cut < printLen and \ - eventString[cut] == ' ': - DebugPrint("-> skipping space <-\n") - cutWidth += 1 - cut += 1 - - DebugPrint("--- cutWidth=%d cut=%d \"%s\"\n" % - (cutWidth, cut, eventString[cut:])) - - nextCutWidth, nextCut, forceCut = \ - self._NextCut(eventString[cut:], cutWidth) - - if forceCut: - DebugPrint("--- forceCut cutWidth=%d cut=%d\n" % (cutWidth, cut)) - break - - cutWidth += nextCutWidth - cut += nextCut - - if eventString[cut] == '\n': - break - - DebugPrint("--- loop cutWidth=%d cut=%d\n" % (cutWidth, cut)) - - return (cutWidth, cut) - - - def _GraphEvents(self, cmd, startDateTime, count, eventList): - - # ignore started events (i.e. that start previous day and end start day) - while (len(eventList) and eventList[0].s < startDateTime): - eventList = eventList[1:] - - dayDivider = '' - for i in xrange(self.calWidth): - dayDivider += '-' - - weekDivider = '' - for i in xrange(7): - weekDivider += '+' - weekDivider += dayDivider - weekDivider += '+' - weekDivider = str(self.borderColor) + weekDivider + str(CLR_NRM()) - - empty = '' - for i in xrange(self.calWidth): - empty += ' ' - - dayFormat = '%-' + str(self.calWidth) + '.' + str(self.calWidth) + 's' - - # Get the localized day names... January 1, 2001 was a Monday - dayNames = [ date(2001, 1, i+1).strftime('%A') for i in range(7) ] - dayNames = dayNames[6:] + dayNames[:6] - - dayHeader = str(self.borderColor) + '|' + str(CLR_NRM()) - for i in xrange(7): - if self.calMonday: - if i == 6: - dayName = dayFormat % (dayNames[0]) - else: - dayName = dayFormat % (dayNames[i+1]) - else: - dayName = dayFormat % (dayNames[i]) - dayHeader += str(self.dateColor) + dayName + str(CLR_NRM()) - dayHeader += str(self.borderColor) + '|' + str(CLR_NRM()) - - PrintMsg(CLR_NRM(), "\n" + weekDivider + "\n") - if cmd == 'calm': - m = startDateTime.strftime('%B %Y') - mw = str((self.calWidth * 7) + 6) - mwf = '%-' + mw + '.' + mw + 's' - PrintMsg(CLR_NRM(), - str(self.borderColor) + '|' + str(CLR_NRM()) + - str(self.dateColor) + mwf % (m) + str(CLR_NRM()) + - str(self.borderColor) + '|' + str(CLR_NRM()) + '\n') - PrintMsg(CLR_NRM(), weekDivider + "\n") - PrintMsg(CLR_NRM(), dayHeader + "\n") - PrintMsg(CLR_NRM(), weekDivider + "\n") - - curMonth = startDateTime.strftime("%b") - - # get date range objects for the first week - if cmd == 'calm': - dayNum = int(startDateTime.strftime("%w")) - if self.calMonday: - dayNum -= 1 - if dayNum < 0: - dayNum = 6 - startDateTime = (startDateTime - timedelta(days=dayNum)) - startWeekDateTime = startDateTime - endWeekDateTime = (startWeekDateTime + timedelta(days=7)) - - for i in xrange(count): - - # create/print date line - line = str(self.borderColor) + '|' + str(CLR_NRM()) - for j in xrange(7): - if cmd == 'calw': - d = (startWeekDateTime + - timedelta(days=j)).strftime("%d %b") - else: # (cmd == 'calm'): - d = (startWeekDateTime + - timedelta(days=j)).strftime("%d") - if curMonth != (startWeekDateTime + \ - timedelta(days=j)).strftime("%b"): - d = '' - todayMarker = '' - if self.now.strftime("%d%b%Y") == \ - (startWeekDateTime + timedelta(days=j)).strftime("%d%b%Y"): - todayMarker = " **" - line += str(self.dateColor) + \ - dayFormat % (d + todayMarker) + \ - str(CLR_NRM()) + \ - str(self.borderColor) + \ - '|' + \ - str(CLR_NRM()) - PrintMsg(CLR_NRM(), line + "\n") - - weekColorStrings = [ '', '', '', '', '', '', '' ] - weekEventStrings = self._GetWeekEventStrings(cmd, curMonth, - startWeekDateTime, - endWeekDateTime, - eventList) - - # convert the strings to unicode for various string ops - for j in xrange(7): - weekEventStrings[j] = unicode(weekEventStrings[j], - locale.getpreferredencoding()) - - # get date range objects for the next week - startWeekDateTime = endWeekDateTime - endWeekDateTime = (endWeekDateTime + timedelta(days=7)) - - while 1: - - done = True - line = str(self.borderColor) + '|' + str(CLR_NRM()) - - for j in xrange(7): - - if weekEventStrings[j] == '': - weekColorStrings[j] = '' - line += empty + \ - str(self.borderColor) + '|' + str(CLR_NRM()) - continue - - if weekEventStrings[j][0] == '\033': - # get/skip over color sequence - weekColorStrings[j] = '' - while (weekEventStrings[j][0] != 'm'): - weekColorStrings[j] += weekEventStrings[j][0] - weekEventStrings[j] = weekEventStrings[j][1:] - weekColorStrings[j] += weekEventStrings[j][0] - weekEventStrings[j] = weekEventStrings[j][1:] - - if weekEventStrings[j][0] == '\n': - weekColorStrings[j] = '' - weekEventStrings[j] = weekEventStrings[j][1:] - line += empty + \ - str(self.borderColor) + '|' + str(CLR_NRM()) - done = False - continue - - weekEventStrings[j] = weekEventStrings[j].lstrip() - - printLen, cut = self._GetCutIndex(weekEventStrings[j]) - padding = ' ' * (self.calWidth - printLen) - - line += weekColorStrings[j] + \ - weekEventStrings[j][:cut] + \ - padding + \ - str(CLR_NRM()) - weekEventStrings[j] = weekEventStrings[j][cut:] - - done = False - line += str(self.borderColor) + '|' + str(CLR_NRM()) - - if done: - break - - PrintMsg(CLR_NRM(), line + "\n") - - PrintMsg(CLR_NRM(), weekDivider + "\n") - - def _tsv(self, startDateTime, eventList): - # tab-separated output for easier shellscripting. - # Format: - # "Date" "start" "end" "Event-Title" "Location" "Eventdescription" - dayFormat = '%F' - - for event in eventList: - tmpDayStr = event.s.strftime(dayFormat) - tmpTimeStr = event.s.strftime("%H:%M") - tmpTimeStp = event.e.strftime("%H:%M") - str = "%s\t%s\t%s\t%s\t%s\t%s" % (tmpDayStr, tmpTimeStr, tmpTimeStp, self._ValidTitle(event.title.text).strip(), event.where[0].value_string, event.content.text ) - str2 = "%s\n" % str.replace('\n', '''\\n''') - sys.stdout.write(str2) - - def _PrintEvents(self, startDateTime, eventList): - - if len(eventList) == 0: - PrintMsg(CLR_YLW(), "\nNo Events Found...\n") - return - - dayFormat = '\n%a %b %d' # 10 chars for day - indent = ' ' # 10 spaces - detailsIndent = ' ' # 19 spaces - day = '' - - for event in eventList: - - if self.ignoreStarted and (event.s < startDateTime): - continue - - tmpDayStr = event.s.strftime(dayFormat) - - if self.military: - timeFormat = '%-5s' - tmpTimeStr = event.s.strftime("%H:%M") - else: - timeFormat = '%-7s' - tmpTimeStr = \ - event.s.strftime("%I:%M").lstrip('0').rjust(5) + \ - event.s.strftime('%p').lower() - - prefix = indent - if tmpDayStr != day: day = prefix = tmpDayStr - PrintMsg(self.dateColor, prefix) - if event.s.hour == 0 and event.s.minute == 0 and \ - event.e.hour == 0 and event.e.minute == 0: - fmt = ' ' + timeFormat + ' %s\n' - PrintMsg(self._CalendarColor(event.gcalcli_cal), fmt % - ('', self._ValidTitle(event.title.text).strip())) - else: - fmt = ' ' + timeFormat + ' %s\n' - PrintMsg(self._CalendarColor(event.gcalcli_cal), fmt % - (tmpTimeStr, self._ValidTitle(event.title.text).strip())) - - if self.details: - - clr = CLR_NRM() - - if event.where[0].value_string: - str = "%s Location: %s\n" % (detailsIndent, - event.where[0].value_string) - PrintMsg(clr, str) - - diffDateTime = (event.e - event.s) - str = "%s Length: %s\n" % (detailsIndent, diffDateTime) - PrintMsg(clr, str) - - # XXX Why does accessing event.when[0].reminder[0] fail? - for rem in event.when[0].reminder: - remStr = '' - if rem.days: - remStr += "%s Days" % (rem.days) - if rem.hours: - if remStr != '': remStr += ' ' - remStr += "%s Hours" % (rem.hours) - if rem.minutes: - if remStr != '': remStr += ' ' - remStr += "%s Minutes" % (rem.minutes) - str = "%s Reminder: %s\n" % (detailsIndent, remStr) - PrintMsg(clr, str) - - if event.content.text: - str = "%s Content: %s\n" % (detailsIndent, - event.content.text) - PrintMsg(clr, str) - - - def _GetAllEvents(self, cal, feed, end): - - eventList = [] - - while 1: - next = feed.GetNextLink() - - for event in feed.entry: - - event.gcalcli_cal = cal - - event.s = parse(event.when[0].start_time) - if event.s.tzinfo == None: - event.s = event.s.replace(tzinfo=tzlocal()) - - event.e = parse(event.when[0].end_time) - if event.e.tzinfo == None: - event.e = event.e.replace(tzinfo=tzlocal()) - - # For all-day events, Google seems to assume that the event time - # is based in the UTC instead of the local timezone. Here we - # filter out those events start beyond a specified end time. - if end and (event.s >= end): - continue - - # http://en.wikipedia.org/wiki/Year_2038_problem - # Catch the year 2038 problem here as the python dateutil module - # can choke throwing a ValueError exception. If either the start - # or end time for an event has a year '>= 2038' dump it. - if event.s.year >= 2038 or event.e.year >= 2039: - continue - - eventList.append(event) - - if not next: - break - - feed = self.gcal.GetCalendarEventFeed(next.href) - - return eventList - - - def _SearchForCalEvents(self, start, end, searchText): - - eventList = [] - - queue = Queue() - threads = [] - - def worker(cal, query): - feed = self.gcal.CalendarQuery(query) - queue.put((cal, feed)) - - for cal in self.cals: - - if not self._CalendarWithinAccess(cal): - continue - - # see http://code.google.com/apis/calendar/reference.html - if not searchText: - query = CalendarEventQuery(cal.gcalcli_username, - cal.gcalcli_visibility, - cal.gcalcli_projection) - query.start_min = start.isoformat() - query.start_max = end.isoformat() - else: - query = CalendarEventQuery(cal.gcalcli_username, - cal.gcalcli_visibility, - cal.gcalcli_projection, - searchText) - if start: # flagged by --ignore-started - # weeds out old but still pulls in started events - query.futureevents = 'true' - - query.singleevents = 'true' - - # we sort later after getting events from all calendars - #query.orderby = 'starttime' - #query.sortorder = 'ascending' - - th = threading.Thread(target=worker, args=(cal, query)) - threads.append(th) - th.start() - - for th in threads: - th.join() - - while not queue.empty(): - cal, feed = queue.get() - eventList.extend(self._GetAllEvents(cal, feed, end)) - - eventList.sort(lambda x, y: cmp(x.s, y.s)) - - return eventList - - - def ListAllCalendars(self): - - accessLen = 0 - - for cal in self.allCals.entry: - length = len(cal.access_level.value) - if length > accessLen: accessLen = length - - if accessLen < len('Access'): accessLen = len('Access') - - format = ' %0' + str(accessLen) + 's %s\n' - - PrintMsg(CLR_BRYLW(), "\n" + format % ('Access', 'Title')) - PrintMsg(CLR_BRYLW(), format % ('------', '-----')) - - for cal in self.allCals.entry: - PrintMsg(self._CalendarColor(cal), - format % (cal.access_level.value, cal.title.text)) - - - def TextQuery(self, searchText=''): - - # the empty string would get *ALL* events... - if searchText == '': - return - - if self.ignoreStarted: - start = self.now # flags gdata futureevents to true - else: - start = None - - # convert now to midnight this morning and use for default - defaultDateTime = self.now.replace(hour=0, - minute=0, - second=0, - microsecond=0) - - eventList = \ - self._SearchForCalEvents(start, None, searchText) - - self._PrintEvents(self.now, eventList) - - - def AgendaQuery(self, startText='', endText=''): - - if self.ignoreStarted: - defaultDateTime = self.now - else: - # convert now to midnight this morning and use for default - defaultDateTime = self.now.replace(hour=0, - minute=0, - second=0, - microsecond=0) - - if startText == '': - start = defaultDateTime - else: - try: - start = parse(startText, default=defaultDateTime) - except: - PrintErrMsg('Error: failed to parse start time\n') - return - - if endText == '': - end = (start + timedelta(days=self.agendaLength)) - else: - try: - end = parse(endText, default=defaultDateTime) - except: - PrintErrMsg('Error: failed to parse end time\n') - return - - eventList = self._SearchForCalEvents(start, end, None) - - if self.tsv: - self._tsv(start, eventList) - else: - self._PrintEvents(start, eventList) - - - def CalQuery(self, cmd, startText='', count=1): - - # convert now to midnight this morning and use for default - defaultDateTime = self.now.replace(hour=0, - minute=0, - second=0, - microsecond=0) - - if startText == '': - start = defaultDateTime - else: - try: - start = parse(startText, default=defaultDateTime) - start = start.replace(hour=0, minute=0, second=0, microsecond=0) - except: - PrintErrMsg('Error: failed to parse start time\n') - return - - # convert start date to the beginning of the week or month - if cmd == 'calw': - dayNum = int(start.strftime("%w")) - if self.calMonday: - dayNum -= 1 - if dayNum < 0: - dayNum = 6 - start = (start - timedelta(days=dayNum)) - end = (start + timedelta(days=(count * 7))) - else: # cmd == 'calm': - start = (start - timedelta(days=(start.day - 1))) - endMonth = (start.month + 1) - endYear = start.year - if endMonth == 13: - endMonth = 1 - endYear += 1 - end = start.replace(month=endMonth, year=endYear) - daysInMonth = (end - start).days - offsetDays = int(start.strftime('%w')) - if self.calMonday: - offsetDays -= 1 - if offsetDays < 0: - offsetDays = 6 - totalDays = (daysInMonth + offsetDays) - count = (totalDays / 7) - if totalDays % 7: - count += 1 - - eventList = self._SearchForCalEvents(start, end, None) - - self._GraphEvents(cmd, start, count, eventList) - - - def QuickAdd(self, eventText): - - if eventText == '': - return - - quickEvent = gdata.calendar.CalendarEventEntry() - quickEvent.content = atom.Content(text=eventText) - quickEvent.quick_add = gdata.calendar.QuickAdd(value='true') - - try: - self.gcal.InsertEvent(quickEvent, self._TargetCalendar()) - except Exception, e: - PrintErrMsg("Error: " + e["reason"] + "\n") - sys.exit(1) - - - def Remind(self, minutes=10, command=None): - - if command == None: - command = self.command - - # perform a date query for now + minutes + slip - start = self.now - end = (start + timedelta(minutes=(minutes + 5))) - - eventList = self._SearchForCalEvents(start, end, None) - - message = '' - - for event in eventList: - - # skip this event if it already started - # XXX maybe add a 2+ minute grace period here... - if event.s < self.now: - continue - - if self.military: - tmpTimeStr = event.s.strftime('%H:%M') - else: - tmpTimeStr = \ - event.s.strftime('%I:%M').lstrip('0') + \ - event.s.strftime('%p').lower() - - message += '%s %s\n' % \ - (tmpTimeStr, self._ValidTitle(event.title.text).strip()) - - if message == '': - return - - cmd = shlex.split(command) - - for i, a in zip(xrange(len(cmd)), cmd): - if a == '%s': - cmd[i] = message - - pid = os.fork() - if not pid: - os.execvp(cmd[0], cmd) - - - def ImportICS(self, verbose=False, icsFile=None): - try: - import vobject - except: - PrintErrMsg('Python vobject module not installed!\n') - sys.exit(1) - - f = sys.stdin - - if icsFile: - try: - f = file(icsFile) - except Exception, e: - PrintErrMsg("Error: " + str(e) + "!\n") - sys.exit(1) - - while True: - - try: - v = vobject.readComponents(f).next() - except StopIteration: - break - - ve = v.vevent - event = gdata.calendar.CalendarEventEntry() - - if hasattr(ve, 'summary'): - DebugPrint("SUMMARY: %s\n" % ve.summary.value) - if verbose: - print "Event........%s" % ve.summary.value - event.title = atom.Title(text=ve.summary.value) - - if hasattr(ve, 'location'): - DebugPrint("LOCATION: %s\n" % ve.location.value) - if verbose: - print "Location.....%s" % ve.location.value - event.where = gdata.calendar.Where(value_string=ve.location.value) - - if not hasattr(ve, 'dtstart') or not hasattr(ve, 'dtend'): - PrintErrMsg("Error: event does not have a dtstart and dtend!\n") - continue - - DebugPrint("DTSTART: %s\n" % ve.dtstart.value.isoformat()) - DebugPrint("DTEND: %s\n" % ve.dtend.value.isoformat()) - if verbose: - print "Start........%s" % ve.dtstart.value.isoformat(' ') - print "End..........%s" % ve.dtend.value.isoformat(' ') - print "Local Start..%s" % ve.dtstart.value.astimezone(tzlocal()) - print "Local End....%s" % ve.dtend.value.astimezone(tzlocal()) - - if hasattr(ve, 'rrule'): - - DebugPrint("RRULE: %s\n" % ve.rrule.value) - if verbose: - print "Recurrence...%s" % ve.rrule.value - # XXX Need to print a NICE recurrence string - #rr = rrulestr(ve.rrule.value) - #print dir(rr) - - # - # In order to add an RRULE using a DTSTART and DTEND in the - # local timezone, there needs to be a TIMEZONE section in the - # recurrence field. Since that is a pain and I'm lazy... as - # a workaround I convert the DTSTART and DTEND to UTC. Google - # handles this properly and keys off the timezone setting of - # the calendar being added to. The event will be shown at the - # correct local time. :-) - # - - if False: - # A TIMEZONE section is needed for this to work XXX - recurrence = \ - "DTSTART;TZID=" + \ - ve.dtstart.value.tzinfo._tzid + ":" + \ - ve.dtstart.value.strftime('%Y%m%dT%H%M%S') + \ - '\r\n' + \ - "DTEND;TZID=" + \ - ve.dtend.value.tzinfo._tzid + ":" + \ - ve.dtend.value.strftime('%Y%m%dT%H%M%S') + \ - '\r\n' + \ - "RRULE:" + ve.rrule.value + '\r\n' - else: - ve.dtstart.value -= ve.dtstart.value.utcoffset() - ve.dtstart.value = ve.dtstart.value.replace(tzinfo=None) - ve.dtend.value -= ve.dtend.value.utcoffset() - ve.dtend.value = ve.dtend.value.replace(tzinfo=None) - recurrence = \ - "DTSTART:" + \ - ve.dtstart.value.strftime('%Y%m%dT%H%M%S') + \ - '\r\n' + \ - "DTEND:" + \ - ve.dtend.value.strftime('%Y%m%dT%H%M%S') + \ - '\r\n' + \ - "RRULE:" + ve.rrule.value + '\r\n' - - DebugPrint("RECURRENCE:\n%s\n" % recurrence) - event.recurrence = \ - gdata.calendar.Recurrence(text=recurrence) - - elif hasattr(ve, 'dtstart') and hasattr(ve, 'dtend'): - - start = ve.dtstart.value.isoformat() - end = ve.dtend.value.isoformat() - event.when = gdata.calendar.When(start_time=start, - end_time=end) - - if hasattr(ve, 'description'): - DebugPrint("DESCRIPTION: %s\n" % ve.description.value) - if verbose: - print "Description:\n%s" % ve.description.value - event.content = atom.Content(text=ve.description.value) - - if not verbose: - try: - self.gcal.InsertEvent(event, self._TargetCalendar()) - except Exception, e: - PrintErrMsg("Error: " + e["reason"] + "\n") - sys.exit(1) - continue - - PrintMsg(CLR_YLW(), "[i]mport [s]kip [q]uit: ") - val = raw_input() - if val == 'i': - try: - self.gcal.InsertEvent(event, self._TargetCalendar()) - except Exception, e: - PrintErrMsg("Error: " + e["reason"] + "\n") - sys.exit(1) - elif val == 's': - continue - elif val == 'q': - sys.exit(0) - else: - PrintErrMsg('Error: invalid input\n') - sys.exit(1) - - -def LoadConfig(configFile): - - config = RawConfigParser() - config.read(os.path.expanduser(configFile)) - return config - - -def GetConfig(config, key, default): - - try: - value = config.get('gcalcli', key) - except: - value = default - - if value and value.startswith('`'): - # Value is a shell command - cmd = value.strip()[1:-1] - parts = [] - for part in cmd.split(): - parts.append(os.path.expanduser(part)) - value = subprocess.check_output(parts).strip() - - return value - - -def GetConfigMultiple(config, key, default): - - try: - values = config.get('gcalcli', key) - except: - values = default - - if values == None: - return [ None ] - - valueList = csv.reader([ values ], - delimiter=',', - quotechar='"', - skipinitialspace=True).next() - return valueList - - -def GetTrueFalse(value): - - if value.lower() == 'false': return False - else: return True - - -def GetColor(value, exitFlag): - - colors = { 'default' : CLR_NRM(), - 'black' : CLR_BLK(), - 'brightblack' : CLR_BRBLK(), - 'red' : CLR_RED(), - 'brightred' : CLR_BRRED(), - 'green' : CLR_GRN(), - 'brightgreen' : CLR_BRGRN(), - 'yellow' : CLR_YLW(), - 'brightyellow' : CLR_BRYLW(), - 'blue' : CLR_BLU(), - 'brightblue' : CLR_BRBLU(), - 'magenta' : CLR_MAG(), - 'brightmagenta' : CLR_BRMAG(), - 'cyan' : CLR_CYN(), - 'brightcyan' : CLR_BRCYN(), - 'white' : CLR_WHT(), - 'brightwhite' : CLR_BRWHT() } - - try: - return colors[value] - except: - if exitFlag: - PrintErrMsg('Error: invalid color name\n') - sys.exit(1) - else: - return None - - -def GetCalColors(calNames): - calNameColors = [] - for idx in xrange(len(calNames)): - i = calNames[idx].rfind('#') - if i != -1: - c = GetColor(calNames[idx][(i+1):], False) - if c: - calNameColors.append(c) - calNames[idx] = calNames[idx][:i] - else: - calNameColors.append(None) - else: - calNameColors.append(None) - return calNames, calNameColors - - -def BowChickaWowWow(): - - try: - opts, args = getopt.getopt(sys.argv[1:], "", - ["help", - "version", - "config=", - "user=", - "pw=", - "cals=", - "cal=", - "24hr", - "details", - "tsv", - "ignore-started", - "width=", - "mon", - "nc", - "cal-owner-color=", - "cal-editor-color=", - "cal-contributor-color=", - "cal-read-color=", - "cal-freebusy-color=", - "date-color=", - "border-color="]) - except getopt.error: - sys.exit(1) - - configFile = '~/.gcalclirc' - - # look for config file override then load the config file - # we do this first because command line args take precedence - for opt, arg in opts: - if opt == "--config": configFile = arg - - cfg = LoadConfig(configFile) - - usr = GetConfig(cfg, 'user', None) - pwd = GetConfig(cfg, 'pw', None) - access = GetConfig(cfg, 'cals', 'all') - calNames = GetConfigMultiple(cfg, 'cal', None) - military = GetTrueFalse(GetConfig(cfg, '24hr', 'false')) - details = GetTrueFalse(GetConfig(cfg, 'details', 'false')) - ignoreStarted = GetTrueFalse(GetConfig(cfg, 'ignore-started', 'false')) - calWidth = int(GetConfig(cfg, 'width', '10')) - calMonday = GetTrueFalse(GetConfig(cfg, 'mon', 'false')) - tsv = GetTrueFalse(GetConfig(cfg, 'tsv', 'false')) - - calOwnerColor = \ - GetColor(GetConfig(cfg, 'cal-owner-color', 'cyan'), True) - calEditorColor = \ - GetColor(GetConfig(cfg, 'cal-editor-color', 'green'), True) - calContributorColor = \ - GetColor(GetConfig(cfg, 'cal-contributor-color', 'default'), True) - calReadColor = \ - GetColor(GetConfig(cfg, 'cal-read-color', 'magenta'), True) - calFreeBusyColor = \ - GetColor(GetConfig(cfg, 'cal-freebusy-color', 'default'), True) - dateColor = \ - GetColor(GetConfig(cfg, 'date-color', 'yellow'), True) - borderColor = \ - GetColor(GetConfig(cfg, 'border-color', 'white'), True) - - # fix wokCalNames when not specified in config file - if len(calNames) == 1 and calNames[0] == None: - calNames = [] - calNameColors = [] - - # Process options - for opt, arg in opts: - - if opt == "--help": - Usage() - - if opt == "--version": - Version() - - elif opt == "--user": - usr = arg - - elif opt == "--pw": - pwd = arg - - elif opt == "--cals": - access = arg - - elif opt == "--cal": - calNames.append(arg) - - elif opt == "--24hr": - military = True - - elif opt == "--details": - details = True - - elif opt == "--ignore-started": - ignoreStarted = True - - elif opt == "--width": - calWidth = int(arg) - - elif opt == "--mon": - calMonday = True - - elif opt == "--nc": - CLR.useColor = False - - elif opt == "--cal-owner-color": - calOwnerColor = GetColor(arg, True) - - elif opt == "--cal-editor-color": - calEditorColor = GetColor(arg, True) - - elif opt == "--cal-contributor-color": - calContributorColor = GetColor(arg, True) - - elif opt == "--cal-read-color": - calReadColor = GetColor(arg, True) - - elif opt == "--cal-freebusy-color": - calFreeBusyColor = GetColor(arg, True) - - elif opt == "--date-color": - dateColor = GetColor(arg, True) - - elif opt == "--border-color": - borderColor = GetColor(arg, True) - - elif opt == "--tsv": - tsv = True - - if usr == None: - PrintErrMsg('Error: must specify a username\n') - sys.exit(1) - - try: - if pwd == None: - pwd = getpass.getpass("Password: ") - except Exception, e: - PrintErrMsg("Error: " + str(e) + "!\n") - sys.exit(1) - - if pwd == None or pwd == '': - PrintErrMsg('Error: must specify a password\n') - sys.exit(1) - - if len(args) == 0: - PrintErrMsg('Error: no command (--help)\n') - sys.exit(1) - - calNames, calNameColors = GetCalColors(calNames) - - gcal = gcalcli(username=usr, - password=pwd, - access=access, - calNames=calNames, - calNameColors=calNameColors, - military=military, - details=details, - ignoreStarted=ignoreStarted, - calWidth=calWidth, - calMonday=calMonday, - calOwnerColor=calOwnerColor, - calEditorColor=calEditorColor, - calContributorColor=calContributorColor, - calReadColor=calReadColor, - calFreeBusyColor=calFreeBusyColor, - dateColor=dateColor, - borderColor=borderColor, - tsv=tsv) - - if args[0] == 'list': - gcal.ListAllCalendars() - - elif args[0] == 'search': - if len(args) != 2: - PrintErrMsg('Error: invalid search string\n') - sys.exit(1) - - # allow unicode strings for input - gcal.TextQuery(unicode(args[1], locale.getpreferredencoding())) - - sys.stdout.write('\n') - - elif args[0] == 'agenda': - if len(args) == 3: # start and end - gcal.AgendaQuery(startText=args[1], endText=args[2]) - elif len(args) == 2: # start - gcal.AgendaQuery(startText=args[1]) - elif len(args) == 1: # defaults - gcal.AgendaQuery() - else: - PrintErrMsg('Error: invalid agenda arguments\n') - sys.exit(1) - - if not tsv: - sys.stdout.write('\n') - - elif args[0] == 'calw': - if not calWidth: - PrintErrMsg('Error: invalid width, don\'t be an idiot!\n') - sys.exit(1) - - if len(args) >= 2: - try: - count = int(args[1]) - except: - PrintErrMsg('Error: invalid calw arguments\n') - sys.exit(1) - - if len(args) == 3: # weeks and start - gcal.CalQuery(args[0], count=int(args[1]), startText=args[2]) - elif len(args) == 2: # weeks - gcal.CalQuery(args[0], count=int(args[1])) - elif len(args) == 1: # defaults - gcal.CalQuery(args[0]) - else: - PrintErrMsg('Error: invalid calw arguments\n') - sys.exit(1) - - sys.stdout.write('\n') - - elif args[0] == 'calm': - if not calWidth: - PrintErrMsg('Error: invalid width, don\'t be an idiot!\n') - sys.exit(1) - - if len(args) == 2: # start - gcal.CalQuery(args[0], startText=args[1]) - elif len(args) == 1: # defaults - gcal.CalQuery(args[0]) - else: - PrintErrMsg('Error: invalid calm arguments\n') - sys.exit(1) - - sys.stdout.write('\n') - - elif args[0] == 'quick': - if len(args) != 2: - PrintErrMsg('Error: invalid event text\n') - sys.exit(1) - - # allow unicode strings for input - gcal.QuickAdd(unicode(args[1], locale.getpreferredencoding())) - - elif args[0] == 'remind': - if len(args) == 3: # minutes and command - gcal.Remind(int(args[1]), args[2]) - elif len(args) == 2: # minutes - gcal.Remind(int(args[1])) - elif len(args) == 1: # defaults - gcal.Remind() - else: - PrintErrMsg('Error: invalid remind arguments\n') - sys.exit(1) - - elif args[0] == 'import': - args = args[1:] - verbose = False - if len(args) >= 1 and args[0] == "-v": - verbose = True - args = args[1:] - if len(args) == 0: # stdin - gcal.ImportICS(verbose) - elif len(args) == 1: # ics file - gcal.ImportICS(verbose, args[0]) - else: - PrintErrMsg('Error: invalid import arguments\n') - sys.exit(1) - - else: - PrintErrMsg('Error: unknown command (--help)\n') - sys.exit(1) - - -if __name__ == '__main__': - BowChickaWowWow() -