You are not logged in.

#41 2016-08-25 12:49:10

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

ohnonot wrote:
WaltH wrote:

I went in and modified the /usr/bin/gcalcli file to use the color1, color2, etc. syntax so that if I modify the colors in Conky, they should then modify in gcalcli.

this will not survive updates and is not recommended.
better way is to copy /usr/bin/calcli to $HOME/bin/calcli and edit that instead. without sudo. it should take precedence over /usr/bin/calcli even when called without the full path.

mind, you will still have to redo this everytime calcli changes and you want to include these changes in your own version. if it's a git repo, you might just fork it, i think git is designed specifically for these types of scenarios.
(but that goes way beyond the scope of this thread)

anyhow, the actual script would be nice to get a link to or paste it here.
see also the other thread.
how about keeping the work to one of these two threads?

Yes, that is what I should have done and will now go back and do. The changes aren't that hard to undo. If you want the actual gcalcli script, here it is (with my modifications - noted to the side):

#!/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 us a beer. :-)
#

# XXX Todo/Cleanup XXX
# threading is currently broken when getting event list
# if threading works then move pageToken processing from GetAllEvents to thread
# support different types of reminders plus multiple ones (popup, sms, email)
# add caching, should be easy (dump all calendar JSON data to file)
# add support for multiline description input in the 'add' and 'edit' commands
# maybe add support for freebusy ?

#############################################################################
#                                                                           #
#                                      (           (     (                  #
#               (         (     (      )\ )   (    )\ )  )\ )               #
#               )\ )      )\    )\    (()/(   )\  (()/( (()/(               #
#              (()/(    (((_)((((_)(   /(_))(((_)  /(_)) /(_))              #
#               /(_))_  )\___ )\ _ )\ (_))  )\___ (_))  (_))                #
#              (_)) __|((/ __|(_)_\(_)| |  ((/ __|| |   |_ _|               #
#                | (_ | | (__  / _ \  | |__ | (__ | |__  | |                #
#                 \___|  \___|/_/ \_\ |____| \___||____||___|               #
#                                                                           #
# Author: Eric Davis <http://www.insanum.com>                               #
#         Brian Hartvigsen <http://github.com/tresni>                       #
# Home: https://github.com/insanum/gcalcli                                  #
#                                                                           #
# Requirements:                                                             #
#  - Python 2                                                               #
#        http://www.python.org                                              #
#  - Google APIs Client Library for Python 2                                #
#        https://developers.google.com/api-client-library/python            #
#  - dateutil Python 2 module                                               #
#        http://www.labix.org/python-dateutil                               #
#                                                                           #
# Optional:                                                                 #
#  - vobject Python module (needed for importing ics/vcal files)            #
#        http://vobject.skyhouseconsulting.com                              #
#  - parsedatetime Python module (needed for fuzzy date parsing)            #
#        https://github.com/bear/parsedatetime                              #
#                                                                           #
# Everything you need to know (Google API Calendar v3): http://goo.gl/HfTGQ #
#                                                                           #
#############################################################################

__program__ = 'gcalcli'
__version__ = 'v3.3.2'
__author__ = 'Eric Davis, Brian Hartvigsen'
__doc__ = '''
Usage:

%s [options] command [command args or options]

 Commands:

  list                     list all calendars

  search <text>            search for events
                           - case insensitive search terms to find events that
                             match these terms in any field, like traditional
                             Google search with quotes, exclusion, etc.
                           - for example to get just games: "soccer -practice"

  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'
                              '24/09/2007'
                              '24/9/07'
                              'Sep 24 2007 3:30pm'
                              '2007-09-24T15:30'
                              '2007-09-24T15:30-8:00'
                              '20070924T15'
                              '8am'

  calw <weeks> [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 <text>             quick add an event to a calendar
                           - a single --calendar must specified
                           - the "--details url" option will show the event link
                           - example text:
                              'Dinner with Eric 7pm tomorrow'
                              '5pm 10/31 Trick or Treat'

  add                      add a detailed event to a calendar
                           - a single --calendar must specified
                           - the "--details url" option will show the event link
                           - example:
                              gcalcli --calendar 'Eric Davis'
                                      --title 'Analysis of Algorithms Final'
                                      --where UCI
                                      --when '12/14/2012 10:00'
                                      --duration 60
                                      --description 'It is going to be hard!'
                                      --reminder 30
                                      add

  delete <text> [start] [end]
                           delete event(s) within the optional time period
                           - case insensitive search terms to find and delete
                             events, just like the 'search' command
                           - deleting is interactive
                             use the --iamaexpert option to auto delete
                             THINK YOU'RE AN EXPERT? USE AT YOUR OWN RISK!!!
                           - use the --details options to show event details
                           - [start] and [end] use the same formats as agenda

  edit <text>              edit event(s)
                           - case insensitive search terms to find and edit
                             events, just like the 'search' command
                           - editing is interactive

  import [file]            import an ics/vcal file to a calendar
                           - a single --calendar must specified
                           - 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
                           - if -d is given then each event in the file is
                             displayed and is not imported, a --calendar does
                             not need to be specified for this option

  remind <mins> <command>  execute command if event occurs within <mins>
                           minutes time ('%%s' in <command> is replaced with
                           event start time and title text)
                           - <mins> default is 10
                           - default command:
                              'notify-send -u critical -a gcalcli %%s'
'''

__API_CLIENT_ID__ = '232867676714.apps.googleusercontent.com'
__API_CLIENT_SECRET__ = '3tZSxItw6_VnZMezQwC8lUqy'

# These are standard libraries and should never fail
import sys
import os
import re
import shlex
import time
import calendar
import locale
import textwrap
import signal
import json
import random
from datetime import datetime, timedelta, date
from unicodedata import east_asian_width

# Required 3rd party libraries
try:
    from dateutil.tz import tzlocal
    from dateutil.parser import parse
    import gflags
    import httplib2
    from apiclient.discovery import build
    from apiclient.errors import HttpError
    from oauth2client.file import Storage
    from oauth2client.client import OAuth2WebServerFlow
    from oauth2client.tools import run_flow
    from oauth2client.tools import argparser
except ImportError as e:
    print "ERROR: Missing module - %s" % e.args[0]
    sys.exit(1)

# cPickle is a standard library, but in case someone did something really
# dumb, fall back to pickle.  If that's not their, your python is fucked
try:
    import cPickle as pickle
except ImportError:
    import pickle

# If they have parsedatetime, we'll use it for fuzzy datetime comparison.  If
# not, we just return a fake failure every time and use only dateutil.
try:
    from parsedatetime import parsedatetime
except:
    class parsedatetime:
        class Calendar:
            def parse(self, string):
                return ([], 0)

locale.setlocale(locale.LC_ALL, "")


def stringToUnicode(string):
    if string:
        return unicode(string, locale.getlocale()[1] or
                       locale.getpreferredencoding(False) or
                       "UTF-8")
    else:
        return u''


def stringFromUnicode(string):
    return string.encode(locale.getlocale()[1] or
                         locale.getpreferredencoding(False) or
                         "UTF-8", "replace")


def Version():
    sys.stdout.write(__program__ + ' ' + __version__ + ' (' + __author__ + ')\n')
    sys.exit(1)


def Usage(expanded=False):
    sys.stdout.write(__doc__ % sys.argv[0])
    if expanded:
        print FLAGS.MainModuleHelp()
    sys.exit(1)


class CLR:

    useColor = True
    conky = False

    def __str__(self):
        return self.color if self.useColor else ""


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 SetConkyColors():
    # XXX these colors should be configurable
    CLR.conky = True
    CLR_NRM.color = ""
    CLR_BLK.color = "${color5}" [ORIGINALLY {color black}]
    CLR_BRBLK.color = "${color black}"
    CLR_RED.color = "${color1}" [ORIGINALLY {color red}]
    CLR_BRRED.color = "${color red}"
    CLR_GRN.color = "${color3}" [ORIGINALLY {color green}]
    CLR_BRGRN.color = "${color green}"
    CLR_YLW.color = "${color4}" [ORIGINALLY {color yellow}]
    CLR_BRYLW.color = "${color yellow}"
    CLR_BLU.color = "${color9}" [ORIGINALLY {color blue}]
    CLR_BRBLU.color = "${color blue}"
    CLR_MAG.color = "${color8}" [ORIGINALLY {color magenta}]
    CLR_BRMAG.color = "${color magenta}"
    CLR_CYN.color = "${color6}" [ORIGINALLY {color cyan}]
    CLR_BRCYN.color = "${color cyan}"
    CLR_WHT.color = "${color2}" [ORIGINALLY {color white}]
    CLR_BRWHT.color = "${color white}"
'--calendar = "Walt Huntsman"#red'     [ADDED BY ME]
'--calendar = "Music"#blue'            [ADDED BY ME]
'--calendar = "Christopher Activities"#white' [ADDED BY ME]
'--calendar = "thuntsma@gmail.com"#black'     [ADDED BY ME]


class ART:

    useArt = True
    fancy = ''
    plain = ''

    def __str__(self):
        return self.fancy if self.useArt else self.plain


class ART_HRZ(ART):
    fancy = '\033(0\x71\033(B'
    plain = '-'


class ART_VRT(ART):
    fancy = '\033(0\x78\033(B'
    plain = '|'


class ART_LRC(ART):
    fancy = '\033(0\x6A\033(B'
    plain = '+'


class ART_URC(ART):
    fancy = '\033(0\x6B\033(B'
    plain = '+'


class ART_ULC(ART):
    fancy = '\033(0\x6C\033(B'
    plain = '+'


class ART_LLC(ART):
    fancy = '\033(0\x6D\033(B'
    plain = '+'


class ART_CRS(ART):
    fancy = '\033(0\x6E\033(B'
    plain = '+'


class ART_LTE(ART):
    fancy = '\033(0\x74\033(B'
    plain = '+'


class ART_RTE(ART):
    fancy = '\033(0\x75\033(B'
    plain = '+'


class ART_BTE(ART):
    fancy = '\033(0\x76\033(B'
    plain = '+'


class ART_UTE(ART):
    fancy = '\033(0\x77\033(B'
    plain = '+'


def PrintErrMsg(msg):
    PrintMsg(CLR_BRRED(), msg)


def PrintMsg(color, msg):
    if isinstance(msg, unicode):
        msg = stringFromUnicode(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
    PrintMsg(CLR_YLW(), msg)


def dprint(obj):
    try:
        from pprint import pprint
        pprint(obj)
    except ImportError:
        print obj


class DateTimeParser:
    def __init__(self):
        self.pdtCalendar = parsedatetime.Calendar()

    def fromString(self, eWhen):
        defaultDateTime = datetime.now(tzlocal()).replace(hour=0,
                                                          minute=0,
                                                          second=0,
                                                          microsecond=0)

        try:
            eTimeStart = parse(eWhen, default=defaultDateTime)
        except:
            struct, result = self.pdtCalendar.parse(eWhen)
            if not result:
                raise ValueError("Date and time is invalid")
            eTimeStart = datetime.fromtimestamp(time.mktime(struct), tzlocal())

        return eTimeStart


def DaysSinceEpoch(dt):
    # Because I hate magic numbers
    __DAYS_IN_SECONDS__ = 24 * 60 * 60
    return calendar.timegm(dt.timetuple()) / __DAYS_IN_SECONDS__


def GetTimeFromStr(eWhen, eDuration=0):
    dtp = DateTimeParser()

    try:
        eTimeStart = dtp.fromString(eWhen)
    except:
        PrintErrMsg('Date and time is invalid!\n')
        sys.exit(1)

    if FLAGS.allday:
        try:
            eTimeStop = eTimeStart + timedelta(days=float(eDuration))
        except:
            PrintErrMsg('Duration time (days) is invalid\n')
            sys.exit(1)

        sTimeStart = eTimeStart.date().isoformat()
        sTimeStop = eTimeStop.date().isoformat()

    else:
        try:
            eTimeStop = eTimeStart + timedelta(minutes=float(eDuration))
        except:
            PrintErrMsg('Duration time (minutes) is invalid\n')
            sys.exit(1)

        sTimeStart = eTimeStart.isoformat()
        sTimeStop = eTimeStop.isoformat()

    return sTimeStart, sTimeStop


def ParseReminder(rem):
    matchObj = re.match(r'^(\d+)([wdhm]?)(?:\s+(popup|email|sms))?$', rem)
    if not matchObj:
        PrintErrMsg('Invalid reminder: ' + rem + '\n')
        sys.exit(1)
    n = int(matchObj.group(1))
    t = matchObj.group(2)
    m = matchObj.group(3)
    if t == 'w':
        n = n * 7 * 24 * 60
    elif t == 'd':
        n = n * 24 * 60
    elif t == 'h':
        n = n * 60

    if not m:
        m = 'popup'

    return n, m


class gcalcli:

    cache = {}
    allCals = []
    allEvents = []
    cals = []
    now = datetime.now(tzlocal())
    agendaLength = 5
    maxRetries = 5
    authHttp = None
    calService = None
    urlService = None
    command = 'notify-send -u critical -a gcalcli %s'
    dateParser = DateTimeParser()

    ACCESS_OWNER = 'owner'
    ACCESS_WRITER = 'writer'
    ACCESS_READER = 'reader'
    ACCESS_FREEBUSY = 'freeBusyReader'

    UNIWIDTH = {'W': 2, 'F': 2, 'N': 1, 'Na': 1, 'H': 1, 'A': 1}

    def __init__(self,
                 calNames=[],
                 calNameColors=[],
                 military=False,
                 detailCalendar=False,
                 detailLocation=False,
                 detailAttendees=False,
                 detailLength=False,
                 detailReminders=False,
                 detailDescr=False,
                 detailDescrWidth=80,
                 detailUrl=None,
                 detailEmail=False,
                 ignoreStarted=False,
                 calWidth=10,
                 calMonday=False,
                 calOwnerColor=CLR_CYN(),
                 calWriterColor=CLR_GRN(),
                 calReaderColor=CLR_MAG(),
                 calFreeBusyColor=CLR_NRM(),
                 dateColor=CLR_YLW(),
                 nowMarkerColor=CLR_BRRED(),
                 borderColor=CLR_WHT(),
                 tsv=False,
                 refreshCache=False,
                 useCache=True,
                 configFolder=None,
                 client_id=__API_CLIENT_ID__,
                 client_secret=__API_CLIENT_SECRET__,
                 defaultReminders=False,
                 allDay=False):

        self.military = military
        self.ignoreStarted = ignoreStarted
        self.calWidth = calWidth
        self.calMonday = calMonday
        self.tsv = tsv
        self.refreshCache = refreshCache
        self.useCache = useCache
        self.defaultReminders = defaultReminders
        self.allDay = allDay

        self.detailCalendar = detailCalendar
        self.detailLocation = detailLocation
        self.detailLength = detailLength
        self.detailReminders = detailReminders
        self.detailDescr = detailDescr
        self.detailDescrWidth = detailDescrWidth
        self.detailUrl = detailUrl
        self.detailAttendees = detailAttendees
        self.detailEmail = detailEmail

        self.calOwnerColor = calOwnerColor
        self.calWriterColor = calWriterColor
        self.calReaderColor = calReaderColor
        self.calFreeBusyColor = calFreeBusyColor
        self.dateColor = dateColor
        self.nowMarkerColor = nowMarkerColor
        self.borderColor = borderColor

        self.configFolder = configFolder

        self.client_id = client_id
        self.client_secret = client_secret

        self._GetCached()

        if len(calNames):
            # Changing the order of this and the `cal in self.allCals` loop
            # is necessary for the matching to actually be sane (ie match
            # supplied name to cached vs matching cache against supplied names)
            for i in xrange(len(calNames)):
                matches = []
                for cal in self.allCals:
                    # For exact match, we should match only 1 entry and accept
                    # the first entry.  Should honor access role order since
                    # it happens after _GetCached()
                    if calNames[i] == cal['summary']:
                        # This makes sure that if we have any regex matches
                        # that we toss them out in favor of the specific match
                        matches = [cal]
                        cal['colorSpec'] = calNameColors[i]
                        break
                    # Otherwise, if the calendar matches as a regex, append
                    # it to the list of potential matches
                    elif re.search(calNames[i], cal['summary'], flags=re.I):
                        matches.append(cal)
                        cal['colorSpec'] = calNameColors[i]
                # Add relevant matches to the list of calendars we want to
                # operate against
                self.cals += matches
        else:
            self.cals = self.allCals

    @staticmethod
    def _LocalizeDateTime(dt):
        if not hasattr(dt, 'tzinfo'):
            return dt
        if dt.tzinfo is None:
            return dt.replace(tzinfo=tzlocal())
        else:
            return dt.astimezone(tzlocal())

    def _RetryWithBackoff(self, method):
        for n in range(0, self.maxRetries):
            try:
                return method.execute()
            except HttpError, e:
                error = json.loads(e.content)
                if error.get('code') == '403' and \
                        error.get('errors')[0].get('reason') \
                        in ['rateLimitExceeded', 'userRateLimitExceeded']:
                    time.sleep((2 ** n) + random.random())
                else:
                    raise

        return None

    def _GoogleAuth(self):
        if not self.authHttp:
            if self.configFolder:
                storage = Storage(os.path.expanduser("%s/oauth" %
                                                     self.configFolder))
            else:
                storage = Storage(os.path.expanduser('~/.gcalcli_oauth'))
            credentials = storage.get()

            if credentials is None or credentials.invalid:
                args, unknown = argparser.parse_known_args(sys.argv)
                credentials = run_flow(
                    OAuth2WebServerFlow(
                        client_id=self.client_id,
                        client_secret=self.client_secret,
                        scope=['https://www.googleapis.com/auth/calendar',
                               'https://www.googleapis.com/auth/urlshortener'],
                        user_agent=__program__ + '/' + __version__),
                    storage, args)

            self.authHttp = credentials.authorize(httplib2.Http())

        return self.authHttp

    def _CalService(self):
        if not self.calService:
            self.calService = \
                build(serviceName='calendar',
                      version='v3',
                      http=self._GoogleAuth())

        return self.calService

    def _UrlService(self):
        if not self.urlService:
            self._GoogleAuth()
            self.urlService = \
                build(serviceName='urlshortener',
                      version='v1',
                      http=self._GoogleAuth())

        return self.urlService

    def _GetCached(self):
        if self.configFolder:
            cacheFile = os.path.expanduser("%s/cache" % self.configFolder)
        else:
            cacheFile = os.path.expanduser('~/.gcalcli_cache')

        if self.refreshCache:
            try:
                os.remove(cacheFile)
            except OSError:
                pass
                # fall through

        self.cache = {}
        self.allCals = []

        if self.useCache:
            # note that we need to use pickle for cache data since we stuff
            # various non-JSON data in the runtime storage structures
            try:
                with open(cacheFile, 'rb') as _cache_:
                    self.cache = pickle.load(_cache_)
                    self.allCals = self.cache['allCals']
                # XXX assuming data is valid, need some verification check here
                return
            except IOError:
                pass
                # fall through

        calList = self._RetryWithBackoff(
            self._CalService().calendarList().list())

        while True:
            for cal in calList['items']:
                self.allCals.append(cal)
            pageToken = calList.get('nextPageToken')
            if pageToken:
                calList = self._RetryWithBackoff(
                    self._CalService().calendarList().list(pageToken=pageToken))
            else:
                break

        # gcalcli defined way to order calendars
        order = {self.ACCESS_OWNER: 1,
                 self.ACCESS_WRITER: 2,
                 self.ACCESS_READER: 3,
                 self.ACCESS_FREEBUSY: 4}

        self.allCals.sort(lambda x, y:
                          cmp(order[x['accessRole']],
                              order[y['accessRole']]))

        if self.useCache:
            self.cache['allCals'] = self.allCals
            with open(cacheFile, 'wb') as _cache_:
                pickle.dump(self.cache, _cache_)

    def _ShortenURL(self, url):
        if self.detailUrl != "short":
            return url
        # Note that when authenticated to a google account different shortUrls
        # can be returned for the same longUrl. See: http://goo.gl/Ya0A9
        shortUrl = self._RetryWithBackoff(
            self._UrlService().url().insert(body={'longUrl': url}))
        return shortUrl['id']

    def _CalendarColor(self, cal):

        if cal is None:
            return CLR_NRM()
        elif 'colorSpec' in cal and cal['colorSpec'] is not None:
            return cal['colorSpec']
        elif cal['accessRole'] == self.ACCESS_OWNER:
            return self.calOwnerColor
        elif cal['accessRole'] == self.ACCESS_WRITER:
            return self.calWriterColor
        elif cal['accessRole'] == self.ACCESS_READER:
            return self.calReaderColor
        elif cal['accessRole'] == self.ACCESS_FREEBUSY:
            return self.calFreeBusyColor
        else:
            return CLR_NRM()

    def _ValidTitle(self, event):
        if 'summary' in event and event['summary'].strip():
            return event['summary']
        else:
            return "(No title)"

    def _GetWeekEventStrings(self, cmd, curMonth,
                             startDateTime, endDateTime, eventList):

        weekEventStrings = ['', '', '', '', '', '', '']

        nowMarkerPrinted = False
        if self.now < startDateTime or self.now > endDateTime:
            # now isn't in this week
            nowMarkerPrinted = True

        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:

                forceEventColorAsMarker = False

                if event['s'].hour == 0 and event['s'].minute == 0 and \
                        event['e'].hour == 0 and event['e'].minute == 0:
                    allDay = True
                else:
                    allDay = False

                if not nowMarkerPrinted:
                    if (DaysSinceEpoch(self.now) <
                            DaysSinceEpoch(event['s'])):
                        nowMarkerPrinted = True
                        weekEventStrings[dayNum - 1] += \
                            ("\n" +
                             str(self.nowMarkerColor) +
                             (self.calWidth * '-'))
                    elif self.now <= event['s']:
                        # add a line marker before next event
                        nowMarkerPrinted = True
                        weekEventStrings[dayNum] += \
                            ("\n" +
                             str(self.nowMarkerColor) +
                             (self.calWidth * '-'))
                    # We don't want to recolor all day events, but ignoring
                    # them leads to issues where the "now" marker misprints
                    # into the wrong day.  This resolves the issue by skipping
                    # all day events for specific coloring but not for previous
                    # or next events
                    elif self.now >= event['s'] and \
                            self.now <= event['e'] and \
                            not allDay:
                        # line marker is during the event (recolor event)
                        nowMarkerPrinted = True
                        forceEventColorAsMarker = True

                if allDay:
                    tmpTimeStr = ''
                elif self.military:
                    tmpTimeStr = event['s'].strftime("%H:%M")
                else:
                    tmpTimeStr = \
                        event['s'].strftime("%I:%M").lstrip('0') + \
                        event['s'].strftime('%p').lower()

                if forceEventColorAsMarker:
                    eventColor = self.nowMarkerColor
                else:
                    eventColor = self._CalendarColor(event['gcalcli_cal'])

                # newline and empty string are the keys to turn off coloring
                weekEventStrings[dayNum] += \
                    "\n" + \
                    stringToUnicode(str(eventColor)) + \
                    stringToUnicode(tmpTimeStr.strip()) + \
                    " " + \
                    self._ValidTitle(event).strip()

        return weekEventStrings

    def _PrintLen(self, string):
        # We need to treat everything as unicode for this to actually give
        # us the info we want.  Date string were coming in as `str` type
        # so we convert them to unicode and then check their size. Fixes
        # the output issues we were seeing around non-US locale strings
        if not isinstance(string, unicode):
            string = stringToUnicode(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
        if not isinstance(string, unicode):
            string = stringToUnicode(string)
        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:
            if '\n' in eventString:
                idx = eventString.find('\n')
                printLen = self._PrintLen(eventString[:idx])
            else:
                idx = len(eventString)

            DebugPrint("------ printLen=%d (end of string)\n" % idx)
            return (printLen, idx)

        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. events that start previous day and end
        # start day)
        while (len(eventList) and eventList[0]['s'] < startDateTime):
            eventList = eventList[1:]

        dayWidthLine = (self.calWidth * str(ART_HRZ()))

        topWeekDivider = (str(self.borderColor) +
                          str(ART_ULC()) + dayWidthLine +
                          (6 * (str(ART_UTE()) + dayWidthLine)) +
                          str(ART_URC()) + str(CLR_NRM()))

        midWeekDivider = (str(self.borderColor) +
                          str(ART_LTE()) + dayWidthLine +
                          (6 * (str(ART_CRS()) + dayWidthLine)) +
                          str(ART_RTE()) + str(CLR_NRM()))

        botWeekDivider = (str(self.borderColor) +
                          str(ART_LLC()) + dayWidthLine +
                          (6 * (str(ART_BTE()) + dayWidthLine)) +
                          str(ART_LRC()) + str(CLR_NRM()))

        empty = self.calWidth * ' '

        # 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(ART_VRT()) + str(CLR_NRM())
        for i in xrange(7):
            if self.calMonday:
                if i == 6:
                    dayName = dayNames[0]
                else:
                    dayName = dayNames[i + 1]
            else:
                dayName = dayNames[i]
            dayName += ' ' * (self.calWidth - self._PrintLen(dayName))
            dayHeader += str(self.dateColor) + dayName + str(CLR_NRM())
            dayHeader += str(self.borderColor) + str(ART_VRT()) + \
                str(CLR_NRM())

        if cmd == 'calm':
            topMonthDivider = (str(self.borderColor) +
                               str(ART_ULC()) + dayWidthLine +
                               (6 * (str(ART_HRZ()) + dayWidthLine)) +
                               str(ART_URC()) + str(CLR_NRM()))
            PrintMsg(CLR_NRM(), "\n" + topMonthDivider + "\n")

            m = startDateTime.strftime('%B %Y')
            mw = (self.calWidth * 7) + 6
            m += ' ' * (mw - self._PrintLen(m))
            PrintMsg(CLR_NRM(),
                     str(self.borderColor) +
                     str(ART_VRT()) +
                     str(CLR_NRM()) +
                     str(self.dateColor) +
                     m +
                     str(CLR_NRM()) +
                     str(self.borderColor) +
                     str(ART_VRT()) +
                     str(CLR_NRM()) +
                     '\n')

            botMonthDivider = (str(self.borderColor) +
                               str(ART_LTE()) + dayWidthLine +
                               (6 * (str(ART_UTE()) + dayWidthLine)) +
                               str(ART_RTE()) + str(CLR_NRM()))
            PrintMsg(CLR_NRM(), botMonthDivider + "\n")

        else:  # calw
            PrintMsg(CLR_NRM(), "\n" + topWeekDivider + "\n")

        PrintMsg(CLR_NRM(), dayHeader + "\n")
        PrintMsg(CLR_NRM(), midWeekDivider + "\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(ART_VRT()) + 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 = ''
                tmpDateColor = self.dateColor

                if self.now.strftime("%d%b%Y") == \
                   (startWeekDateTime + timedelta(days=j)).strftime("%d%b%Y"):
                    tmpDateColor = self.nowMarkerColor
                    d += " **"

                d += ' ' * (self.calWidth - self._PrintLen(d))
                line += str(tmpDateColor) + \
                    d + \
                    str(CLR_NRM()) + \
                    str(self.borderColor) + \
                    str(ART_VRT()) + \
                    str(CLR_NRM())
            PrintMsg(CLR_NRM(), line + "\n")

            weekColorStrings = ['', '', '', '', '', '', '']
            weekEventStrings = self._GetWeekEventStrings(cmd, curMonth,
                                                         startWeekDateTime,
                                                         endWeekDateTime,
                                                         eventList)

            # get date range objects for the next week
            startWeekDateTime = endWeekDateTime
            endWeekDateTime = (endWeekDateTime + timedelta(days=7))

            while 1:

                done = True
                line = str(self.borderColor) + str(ART_VRT()) + str(CLR_NRM())

                for j in xrange(7):

                    if weekEventStrings[j] == '':
                        weekColorStrings[j] = ''
                        line += (empty +
                                 str(self.borderColor) +
                                 str(ART_VRT()) +
                                 str(CLR_NRM()))
                        continue

                    # get/skip over a color sequence
                    if ((not CLR.conky and weekEventStrings[j][0] == '\033') or
                            (CLR.conky and weekEventStrings[j][0] == '$')):
                        weekColorStrings[j] = ''
                        while ((not CLR.conky and
                                weekEventStrings[j][0] != 'm') or
                                (CLR.conky and weekEventStrings[j][0] != '}')):
                            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(ART_VRT()) +
                                 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(ART_VRT()) +
                             str(CLR_NRM()))

                if done:
                    break

                PrintMsg(CLR_NRM(), line + "\n")

            if i < range(count)[len(range(count)) - 1]:
                PrintMsg(CLR_NRM(), midWeekDivider + "\n")
            else:
                PrintMsg(CLR_NRM(), botWeekDivider + "\n")

    def _tsv(self, startDateTime, eventList):
        for event in eventList:
            output = "%s\t%s\t%s\t%s" % (event['s'].strftime('%Y-%m-%d'),
                                         event['s'].strftime('%H:%M'),
                                         event['e'].strftime('%Y-%m-%d'),
                                         event['e'].strftime('%H:%M'))

            if self.detailUrl:
                output += "\t%s" % (self._ShortenURL(event['htmlLink'])
                                    if 'htmlLink' in event else '')
                output += "\t%s" % (self._ShortenURL(event['hangoutLink'])
                                    if 'hangoutLink' in event else '')

            output += "\t%s" % self._ValidTitle(event).strip()

            if self.detailLocation:
                output += "\t%s" % (event['location'].strip()
                                    if 'location' in event else '')

            if self.detailDescr:
                output += "\t%s" % (event['description'].strip()
                                    if 'description' in event else '')

            if self.detailCalendar:
                output += "\t%s" % event['gcalcli_cal']['summary'].strip()

            if self.detailEmail:
                output += "\t%s" % (event['creator']['email'].strip()
                                    if 'email' in event['creator'] else '')

            output = "%s\n" % output.replace('\n', '''\\n''')
            sys.stdout.write(stringFromUnicode(output))

    def _PrintEvent(self, event, prefix):

        def _formatDescr(descr, indent, box):
            wrapper = textwrap.TextWrapper()
            if box:
                wrapper.initial_indent = (indent + '  ')
                wrapper.subsequent_indent = (indent + '  ')
                wrapper.width = (self.detailDescrWidth - 2)
            else:
                wrapper.initial_indent = indent
                wrapper.subsequent_indent = indent
                wrapper.width = self.detailDescrWidth
            new_descr = ""
            for line in descr.split("\n"):
                if box:
                    tmpLine = wrapper.fill(line)
                    for singleLine in tmpLine.split("\n"):
                        singleLine = singleLine.ljust(self.detailDescrWidth,
                                                      ' ')
                        new_descr += singleLine[:len(indent)] + \
                            str(ART_VRT()) + \
                            singleLine[(len(indent) + 1):
                                       (self.detailDescrWidth - 1)] + \
                            str(ART_VRT()) + '\n'
                else:
                    new_descr += wrapper.fill(line) + "\n"
            return new_descr.rstrip()

        indent = 10 * ' '
        detailsIndent = 19 * ' '

        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()

        if not prefix:
            prefix = indent

        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).strip()))
        else:
            fmt = '  ' + timeFormat + '  %s\n'
            PrintMsg(self._CalendarColor(event['gcalcli_cal']), fmt %
                     (stringToUnicode(tmpTimeStr), self._ValidTitle(event).strip()))

        if self.detailCalendar:
            xstr = "%s  Calendar: %s\n" % (
                detailsIndent,
                event['gcalcli_cal']['summary']
            )
            PrintMsg(CLR_NRM(), xstr)

        if self.detailUrl and 'htmlLink' in event:
            hLink = self._ShortenURL(event['htmlLink'])
            xstr = "%s  Link: %s\n" % (detailsIndent, hLink)
            PrintMsg(CLR_NRM(), xstr)

        if self.detailUrl and 'hangoutLink' in event:
            hLink = self._ShortenURL(event['hangoutLink'])
            xstr = "%s  Hangout Link: %s\n" % (detailsIndent, hLink)
            PrintMsg(CLR_NRM(), xstr)

        if self.detailLocation and \
           'location' in event and \
           event['location'].strip():
            xstr = "%s  Location: %s\n" % (
                detailsIndent,
                event['location'].strip()
            )
            PrintMsg(CLR_NRM(), xstr)

        if self.detailAttendees and 'attendees' in event:
            xstr = "%s  Attendees:\n" % (detailsIndent)
            PrintMsg(CLR_NRM(), xstr)

            if 'self' not in event['organizer']:
                xstr = "%s    %s: <%s>\n" % (
                    detailsIndent,
                    event['organizer'].get('displayName', 'Not Provided')
                                      .strip(),
                    event['organizer']['email'].strip()
                )
                PrintMsg(CLR_NRM(), xstr)

            for attendee in event['attendees']:
                if 'self' not in attendee:
                    xstr = "%s    %s: <%s>\n" % (
                        detailsIndent,
                        attendee.get('displayName', 'Not Provided').strip(),
                        attendee['email'].strip()
                    )
                    PrintMsg(CLR_NRM(), xstr)

        if self.detailLength:
            diffDateTime = (event['e'] - event['s'])
            xstr = "%s  Length: %s\n" % (detailsIndent, diffDateTime)
            PrintMsg(CLR_NRM(), xstr)

        if self.detailReminders and 'reminders' in event:
            if event['reminders']['useDefault'] is True:
                xstr = "%s  Reminder: (default)\n" % (detailsIndent)
                PrintMsg(CLR_NRM(), xstr)
            elif 'overrides' in event['reminders']:
                for rem in event['reminders']['overrides']:
                    xstr = "%s  Reminder: %s %d minutes\n" % \
                           (detailsIndent, rem['method'], rem['minutes'])
                    PrintMsg(CLR_NRM(), xstr)

        if self.detailEmail and \
           'email' in event['creator'] and \
           event['creator']['email'].strip():
            xstr = "%s  Email: %s\n" % (
                detailsIndent,
                event['creator']['email'].strip()
            )
            PrintMsg(CLR_NRM(), xstr)

        if self.detailDescr and \
           'description' in event and \
           event['description'].strip():
            descrIndent = detailsIndent + '  '
            box = True  # leave old non-box code for option later
            if box:
                topMarker = (descrIndent +
                             str(ART_ULC()) +
                             (str(ART_HRZ()) *
                              ((self.detailDescrWidth - len(descrIndent)) -
                               2)) +
                             str(ART_URC()))
                botMarker = (descrIndent +
                             str(ART_LLC()) +
                             (str(ART_HRZ()) *
                              ((self.detailDescrWidth - len(descrIndent)) -
                               2)) +
                             str(ART_LRC()))
                xstr = "%s  Description:\n%s\n%s\n%s\n" % (
                    detailsIndent,
                    topMarker,
                    _formatDescr(event['description'].strip(),
                                 descrIndent, box),
                    botMarker
                )
            else:
                marker = descrIndent + '-' * \
                    (self.detailDescrWidth - len(descrIndent))
                xstr = "%s  Description:\n%s\n%s\n%s\n" % (
                    detailsIndent,
                    marker,
                    _formatDescr(event['description'].strip(),
                                 descrIndent, box),
                    marker
                )
            PrintMsg(CLR_NRM(), xstr)

    def _DeleteEvent(self, event):

        if self.iamaExpert:
            self._RetryWithBackoff(
                self._CalService().events().
                delete(calendarId=event['gcalcli_cal']['id'],
                       eventId=event['id']))
            PrintMsg(CLR_RED(), "Deleted!\n")
            return

        PrintMsg(CLR_MAG(), "Delete? [N]o [y]es [q]uit: ")
        val = raw_input()

        if not val or val.lower() == 'n':
            return

        elif val.lower() == 'y':
            self._RetryWithBackoff(
                self._CalService().events().
                delete(calendarId=event['gcalcli_cal']['id'],
                       eventId=event['id']))
            PrintMsg(CLR_RED(), "Deleted!\n")

        elif val.lower() == 'q':
            sys.stdout.write('\n')
            sys.exit(0)

        else:
            PrintErrMsg('Error: invalid input\n')
            sys.stdout.write('\n')
            sys.exit(1)

    def _EditEvent(self, event):

        while True:

            PrintMsg(CLR_MAG(), "Edit?\n" +
                                "[N]o [s]ave [q]uit " +
                                "[t]itle [l]ocation " +
                                "[w]hen len[g]th " +
                                "[r]eminder [d]escr: ")
            val = raw_input()

            if not val or val.lower() == 'n':
                return

            elif val.lower() == 's':
                # copy only editable event details for patching
                modEvent = {}
                keys = ['summary', 'location', 'start', 'end',
                        'reminders', 'description']
                for k in keys:
                    if k in event:
                        modEvent[k] = event[k]

                self._RetryWithBackoff(
                    self._CalService().events().
                    patch(calendarId=event['gcalcli_cal']['id'],
                          eventId=event['id'],
                          body=modEvent))
                PrintMsg(CLR_RED(), "Saved!\n")
                return

            elif not val or val.lower() == 'q':
                sys.stdout.write('\n')
                sys.exit(0)

            elif val.lower() == 't':
                PrintMsg(CLR_MAG(), "Title: ")
                val = raw_input()
                if val.strip():
                    event['summary'] = \
                        stringToUnicode(val.strip())

            elif val.lower() == 'l':
                PrintMsg(CLR_MAG(), "Location: ")
                val = raw_input()
                if val.strip():
                    event['location'] = \
                        stringToUnicode(val.strip())

            elif val.lower() == 'w':
                PrintMsg(CLR_MAG(), "When: ")
                val = raw_input()
                if val.strip():
                    td = (event['e'] - event['s'])
                    length = ((td.days * 1440) + (td.seconds / 60))
                    newStart, newEnd = GetTimeFromStr(val.strip(), length)
                    event['s'] = parse(newStart)
                    event['e'] = parse(newEnd)

                    if self.allDay:
                        event['start'] = {'date': newStart,
                                          'dateTime': None,
                                          'timeZone': None}
                        event['end'] = {'date': newEnd,
                                        'dateTime': None,
                                        'timeZone': None}

                    else:
                        event['start'] = {'date': None,
                                          'dateTime': newStart,
                                          'timeZone': event['gcalcli_cal']['timeZone']}
                        event['end'] = {'date': None,
                                        'dateTime': newEnd,
                                        'timeZone': event['gcalcli_cal']['timeZone']}

            elif val.lower() == 'g':
                PrintMsg(CLR_MAG(), "Length (mins): ")
                val = raw_input()
                if val.strip():
                    newStart, newEnd = \
                        GetTimeFromStr(event['start']['dateTime'], val.strip())
                    event['s'] = parse(newStart)
                    event['e'] = parse(newEnd)

                    if self.allDay:
                        event['start'] = {'date': newStart,
                                          'dateTime': None,
                                          'timeZone': None}
                        event['end'] = {'date': newEnd,
                                        'dateTime': None,
                                        'timeZone': None}

                    else:
                        event['start'] = {'date': None,
                                          'dateTime': newStart,
                                          'timeZone': event['gcalcli_cal']['timeZone']}
                        event['end'] = {'date': None,
                                        'dateTime': newEnd,
                                        'timeZone': event['gcalcli_cal']['timeZone']}

            elif val.lower() == 'r':
                rem = []
                while 1:
                    PrintMsg(CLR_MAG(),
                             "Enter a valid reminder or '.' to end: ")
                    r = raw_input()
                    if r == '.':
                        break
                    rem.append(r)

                if rem or not self.defaultReminders:
                    event['reminders'] = {'useDefault': False,
                                          'overrides': []}
                    for r in rem:
                        n, m = ParseReminder(r)
                        event['reminders']['overrides'].append({'minutes': n,
                                                                'method': m})
                else:
                    event['reminders'] = {'useDefault': True,
                                          'overrides': []}

            elif val.lower() == 'd':
                PrintMsg(CLR_MAG(), "Description: ")
                val = raw_input()
                if val.strip():
                    event['description'] = \
                        stringToUnicode(val.strip())

            else:
                PrintErrMsg('Error: invalid input\n')
                sys.stdout.write('\n')
                sys.exit(1)

            self._PrintEvent(event, event['s'].strftime('\n%Y-%m-%d'))

    def _IterateEvents(self, startDateTime, eventList,
                       yearDate=False, work=None):

        if len(eventList) == 0:
            PrintMsg(CLR_YLW(), "\nNo Events Found...\n")
            return

        # 10 chars for day and length must match 'indent' in _PrintEvent
        dayFormat = '\n%Y-%m-%d' if yearDate else '\n%a %b %d'
        day = ''

        for event in eventList:

            if self.ignoreStarted and (event['s'] < self.now):
                continue

            tmpDayStr = event['s'].strftime(dayFormat)
            prefix = None
            if yearDate or tmpDayStr != day:
                day = prefix = tmpDayStr

            self._PrintEvent(event, prefix)

            if work:
                work(event)

    def _GetAllEvents(self, cal, events, end):

        eventList = []

        while 1:
            if 'items' not in events:
                break

            for event in events['items']:

                event['gcalcli_cal'] = cal

                if 'status' in event and event['status'] == 'cancelled':
                    continue

                if 'dateTime' in event['start']:
                    event['s'] = parse(event['start']['dateTime'])
                else:
                    # all date events
                    event['s'] = parse(event['start']['date'])

                event['s'] = self._LocalizeDateTime(event['s'])

                if 'dateTime' in event['end']:
                    event['e'] = parse(event['end']['dateTime'])
                else:
                    # all date events
                    event['e'] = parse(event['end']['date'])

                event['e'] = self._LocalizeDateTime(event['e'])

                # 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 >= 2038:
                    continue

                eventList.append(event)

            pageToken = events.get('nextPageToken')
            if pageToken:
                events = self._RetryWithBackoff(
                    self._CalService().events().
                    list(calendarId=cal['id'], pageToken=pageToken))
            else:
                break

        return eventList

    def _SearchForCalEvents(self, start, end, searchText):

        eventList = []
        for cal in self.cals:
            work = self._CalService().events().\
                list(calendarId=cal['id'],
                     timeMin=start.isoformat() if start else None,
                     timeMax=end.isoformat() if end else None,
                     q=searchText if searchText else None,
                     singleEvents=True)
            events = self._RetryWithBackoff(work)
            eventList.extend(self._GetAllEvents(cal, events, end))

        eventList.sort(lambda x, y: cmp(x['s'], y['s']))

        return eventList

    def ListAllCalendars(self):

        accessLen = 0

        for cal in self.allCals:
            length = len(cal['accessRole'])
            if length > accessLen:
                accessLen = length

        if accessLen < len('Access'):
            accessLen = len('Access')

        format = ' %0' + str(accessLen) + 's  %s\n'

        PrintMsg(CLR_BRYLW(), format % ('Access', 'Title'))
        PrintMsg(CLR_BRYLW(), format % ('------', '-----'))

        for cal in self.allCals:
            PrintMsg(self._CalendarColor(cal),
                     format % (cal['accessRole'], cal['summary']))

    def TextQuery(self, searchText=''):

        # the empty string would get *ALL* events...
        if searchText == '':
            return

        # This is really just an optimization to the gcalendar api
        # why ask for a bunch of events we are going to filter out
        # anyway?
        # TODO: Look at moving this into the _SearchForCalEvents
        #       Don't forget to clean up AgendaQuery too!

        start = self.now if self.ignoreStarted else None
        eventList = self._SearchForCalEvents(start, None, searchText)

        if self.tsv:
            self._tsv(self.now, eventList)
        else:
            self._IterateEvents(self.now, eventList, yearDate=True)

    def AgendaQuery(self, startText='', endText=''):

        if startText == '':
            # convert now to midnight this morning and use for default
            start = self.now.replace(hour=0,
                                     minute=0,
                                     second=0,
                                     microsecond=0)
        else:
            try:
                start = self.dateParser.fromString(startText)
            except:
                PrintErrMsg('Error: failed to parse start time\n')
                return

        # Again optimizing calls to the api.  If we've been told to
        # ignore started events, then it doesn't make ANY sense to
        # search for things that may be in the past
        if self.ignoreStarted and start < self.now:
            start = self.now

        if endText == '':
            end = (start + timedelta(days=self.agendaLength))
        else:
            try:
                end = self.dateParser.fromString(endText)
            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._IterateEvents(start, eventList, yearDate=False)

    def CalQuery(self, cmd, startText='', count=1):

        if startText == '':
            # convert now to midnight this morning and use for default
            start = self.now.replace(hour=0,
                                     minute=0,
                                     second=0,
                                     microsecond=0)
        else:
            try:
                start = self.dateParser.fromString(startText)
                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 QuickAddEvent(self, eventText, reminder=None):

        if eventText == '':
            return

        if len(self.cals) != 1:
            PrintErrMsg("Must specify a single calendar\n")
            return

        newEvent = self._RetryWithBackoff(
            self._CalService().events().quickAdd(calendarId=self.cals[0]['id'],
                                                 text=eventText))

        if reminder or not self.defaultReminders:
            rem = {}
            rem['reminders'] = {'useDefault': False,
                                'overrides': []}
            for r in reminder:
                n, m = ParseReminder(r)
                rem['reminders']['overrides'].append({'minutes': n,
                                                      'method': m})

            newEvent = self._RetryWithBackoff(
                self._CalService().events().
                patch(calendarId=self.cals[0]['id'],
                      eventId=newEvent['id'],
                      body=rem))

        if self.detailUrl:
            hLink = self._ShortenURL(newEvent['htmlLink'])
            PrintMsg(CLR_GRN(), 'New event added: %s\n' % hLink)

    def AddEvent(self, eTitle, eWhere, eStart, eEnd, eDescr, reminder):

        if len(self.cals) != 1:
            PrintErrMsg("Must specify a single calendar\n")
            return

        event = {}
        event['summary'] = stringToUnicode(eTitle)

        if self.allDay:
            event['start'] = {'date': eStart}
            event['end'] = {'date': eEnd}

        else:
            event['start'] = {'dateTime': eStart,
                              'timeZone': self.cals[0]['timeZone']}
            event['end'] = {'dateTime': eEnd,
                            'timeZone': self.cals[0]['timeZone']}

        if eWhere:
            event['location'] = stringToUnicode(eWhere)
        if eDescr:
            event['description'] = stringToUnicode(eDescr)

        if reminder or not self.defaultReminders:
            event['reminders'] = {'useDefault': False,
                                  'overrides': []}
            for r in reminder:
                n, m = ParseReminder(r)
                event['reminders']['overrides'].append({'minutes': n,
                                                        'method': m})

        newEvent = self._RetryWithBackoff(
            self._CalService().events().
            insert(calendarId=self.cals[0]['id'], body=event))

        if self.detailUrl:
            hLink = self._ShortenURL(newEvent['htmlLink'])
            PrintMsg(CLR_GRN(), 'New event added: %s\n' % hLink)

    def DeleteEvents(self, searchText='', expert=False, start=None, end=None):

        # the empty string would get *ALL* events...
        if searchText == '':
            return

        eventList = self._SearchForCalEvents(start, end, searchText)

        self.iamaExpert = expert
        self._IterateEvents(self.now, eventList,
                            yearDate=True, work=self._DeleteEvent)

    def EditEvents(self, searchText=''):

        # the empty string would get *ALL* events...
        if searchText == '':
            return

        eventList = self._SearchForCalEvents(None, None, searchText)

        self._IterateEvents(self.now, eventList,
                            yearDate=True, work=self._EditEvent)

    def Remind(self, minutes=10, command=None, use_reminders=False):
        """Check for events between now and now+minutes.
           If use_reminders then only remind if now >= event['start'] - reminder
    """

        if command is 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

            # not sure if 'reminders' always in event
            if use_reminders and 'reminders' in event and 'overrides' in event['reminders']:
                if all(event['s'] - timedelta(minutes=r['minutes']) > self.now
                   for r in event['reminders']['overrides']):
                    continue   # don't remind if all reminders haven't arrived yet

            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).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, dump=False, reminder=None,
                  icsFile=None):

        def CreateEventFromVOBJ(ve):

            event = {}

            if verbose:
                print "+----------------+"
                print "| Calendar Event |"
                print "+----------------+"

            if hasattr(ve, 'summary'):
                DebugPrint("SUMMARY: %s\n" % ve.summary.value)
                if verbose:
                    print "Event........%s" % ve.summary.value
                event['summary'] = ve.summary.value

            if hasattr(ve, 'location'):
                DebugPrint("LOCATION: %s\n" % ve.location.value)
                if verbose:
                    print "Location.....%s" % ve.location.value
                event['location'] = ve.location.value

            if not hasattr(ve, 'dtstart') or not hasattr(ve, 'dtend'):
                PrintErrMsg("Error: event does not have a dtstart and "
                            "dtend!\n")
                return None

            if ve.dtstart.value:
                DebugPrint("DTSTART: %s\n" % ve.dtstart.value.isoformat())
            if ve.dtend.value:
                DebugPrint("DTEND: %s\n" % ve.dtend.value.isoformat())
            if verbose:
                if ve.dtstart.value:
                    print "Start........%s" % \
                        ve.dtstart.value.isoformat()
                if ve.dtend.value:
                    print "End..........%s" % \
                        ve.dtend.value.isoformat()
                if ve.dtstart.value:
                    print "Local Start..%s" % \
                        self._LocalizeDateTime(ve.dtstart.value)
                if ve.dtend.value:
                    print "Local End....%s" % \
                        self._LocalizeDateTime(ve.dtend.value)

            if hasattr(ve, 'rrule'):

                DebugPrint("RRULE: %s\n" % ve.rrule.value)
                if verbose:
                    print "Recurrence...%s" % ve.rrule.value

                event['recurrence'] = ["RRULE:" + ve.rrule.value]

            if hasattr(ve, 'dtstart') and ve.dtstart.value:
                # XXX
                # Timezone madness! Note that we're using the timezone for the
                # calendar being added to. This is OK if the event is in the
                # same timezone. This needs to be changed to use the timezone
                # from the DTSTART and DTEND values. Problem is, for example,
                # the TZID might be "Pacific Standard Time" and Google expects
                # a timezone string like "America/Los_Angeles". Need to find
                # a way in python to convert to the more specific timezone
                # string.
                # XXX
                # print ve.dtstart.params['X-VOBJ-ORIGINAL-TZID'][0]
                # print self.cals[0]['timeZone']
                # print dir(ve.dtstart.value.tzinfo)
                # print vars(ve.dtstart.value.tzinfo)

                start = ve.dtstart.value.isoformat()
                if isinstance(ve.dtstart.value, datetime):
                    event['start'] = {'dateTime': start,
                                      'timeZone': self.cals[0]['timeZone']}
                else:
                    event['start'] = {'date': start}

                if reminder or not self.defaultReminders:
                    event['reminders'] = {'useDefault': False,
                                          'overrides': []}
                    for r in reminder:
                        n, m = ParseReminder(r)
                        event['reminders']['overrides'].append({'minutes': n,
                                                                'method': m})

                # Can only have an end if we have a start, but not the other
                # way around apparently...  If there is no end, use the start
                if hasattr(ve, 'dtend') and ve.dtend.value:
                    end = ve.dtend.value.isoformat()
                    if isinstance(ve.dtend.value, datetime):
                        event['end'] = {'dateTime': end,
                                        'timeZone': self.cals[0]['timeZone']}
                    else:
                        event['end'] = {'date': end}

                else:
                    event['end'] = event['start']

            if hasattr(ve, 'description') and ve.description.value.strip():
                descr = ve.description.value.strip()
                DebugPrint("DESCRIPTION: %s\n" % descr)
                if verbose:
                    print "Description:\n%s" % descr
                event['description'] = descr

            if hasattr(ve, 'organizer'):
                DebugPrint("ORGANIZER: %s\n" % ve.organizer.value)

                if ve.organizer.value.startswith("MAILTO:"):
                    email = ve.organizer.value[7:]
                else:
                    email = ve.organizer.value
                if verbose:
                    print "organizer:\n %s" % email
                event['organizer'] = {'displayName': ve.organizer.name,
                                      'email': email}

            if hasattr(ve, 'attendee_list'):
                DebugPrint("ATTENDEE_LIST : %s\n" % ve.attendee_list)
                if verbose:
                    print "attendees:"
                event['attendees'] = []
                for attendee in ve.attendee_list:
                    if attendee.value.upper().startswith("MAILTO:"):
                        email = attendee.value[7:]
                    else:
                        email = attendee.value
                    if verbose:
                        print " %s" % email

                    event['attendees'].append({'displayName': attendee.name,
                                               'email': email})

            return event

        try:
            import vobject
        except:
            PrintErrMsg('Python vobject module not installed!\n')
            sys.exit(1)

        if dump:
            verbose = True

        if not dump and len(self.cals) != 1:
            PrintErrMsg("Must specify a single calendar\n")
            return

        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

            for ve in v.vevent_list:

                event = CreateEventFromVOBJ(ve)

                if not event:
                    continue

                if dump:
                    continue

                if not verbose:
                    newEvent = self._RetryWithBackoff(
                        self._CalService().events().
                        insert(calendarId=self.cals[0]['id'],
                               body=event))
                    hLink = self._ShortenURL(newEvent['htmlLink'])
                    PrintMsg(CLR_GRN(), 'New event added: %s\n' % hLink)
                    continue

                PrintMsg(CLR_MAG(), "\n[S]kip [i]mport [q]uit: ")
                val = raw_input()
                if not val or val.lower() == 's':
                    continue
                if val.lower() == 'i':
                    newEvent = self._RetryWithBackoff(
                        self._CalService().events().
                        insert(calendarId=self.cals[0]['id'],
                               body=event))
                    hLink = self._ShortenURL(newEvent['htmlLink'])
                    PrintMsg(CLR_GRN(), 'New event added: %s\n' % hLink)
                elif val.lower() == 'q':
                    sys.exit(0)
                else:
                    PrintErrMsg('Error: invalid input\n')
                    sys.exit(1)


def GetColor(value):
    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(),
              None: CLR_NRM()}

    if value in colors:
        return colors[value]
    else:
        return None


def GetCalColors(calNames):
    calColors = {}
    for calName in calNames:
        calNameParts = calName.split("#")
        calNameSimple = calNameParts[0]
        calColor = calColors.get(calNameSimple)
        if len(calNameParts) > 0:
            calColorRaw = calNameParts[-1]
            calColorNew = GetColor(calColorRaw)
            if calColorNew is not None:
                calColor = calColorNew
        calColors[calNameSimple] = calColor
    return calColors


FLAGS = gflags.FLAGS
# allow mixing of commands and options
FLAGS.UseGnuGetOpt()

gflags.DEFINE_bool("help", None, "Show this help")
gflags.DEFINE_bool("helpshort", None, "Show command help only")
gflags.DEFINE_bool("version", False, "Show the version and exit")

gflags.DEFINE_string("client_id", __API_CLIENT_ID__, "API client_id")
gflags.DEFINE_string("client_secret", __API_CLIENT_SECRET__,
                     "API client_secret")

gflags.DEFINE_string("configFolder", None,
                     "Optional directory to load/store all configuration "
                     "information")
gflags.DEFINE_bool("includeRc", False,
                   "Whether to include ~/.gcalclirc when using configFolder")
gflags.DEFINE_multistring("calendar", [], "Which calendars to use")
gflags.DEFINE_multistring("defaultCalendar", [],
                          "Optional default calendar to use if no --calendar "
                          "options are given")
gflags.DEFINE_bool("military", False, "Use 24 hour display")

# Single --detail that allows you to specify what parts you want
gflags.DEFINE_multistring("details", [], "Which parts to display, can be: "
                          "'all', 'calendar', 'location', 'length', "
                          "'reminders', 'description', 'longurl', 'shorturl', "
                          "'url', 'attendees', 'email'")
# old style flags for backwards compatibility
gflags.DEFINE_bool("detail_all", False, "Display all details")
gflags.DEFINE_bool("detail_calendar", False, "Display calendar name")
gflags.DEFINE_bool("detail_location", False, "Display event location")
gflags.DEFINE_bool("detail_attendees", False, "Display event attendees")
gflags.DEFINE_bool("detail_length", False, "Display length of event")
gflags.DEFINE_bool("detail_reminders", False, "Display reminders")
gflags.DEFINE_bool("detail_description", False, "Display description")
gflags.DEFINE_bool("detail_email", False, "Display creator email")
gflags.DEFINE_integer("detail_description_width", 80, "Set description width")
gflags.DEFINE_enum("detail_url", None, ["long", "short"], "Set URL output")

gflags.DEFINE_bool("tsv", False, "Use Tab Separated Value output")
gflags.DEFINE_bool("started", True, "Show events that have started")
gflags.DEFINE_integer("width", 10, "Set output width", short_name="w")
gflags.DEFINE_bool("monday", False, "Start the week on Monday")
gflags.DEFINE_bool("color", True, "Enable/Disable all color output")
gflags.DEFINE_bool("lineart", True, "Enable/Disable line art")
gflags.DEFINE_bool("conky", False, "Use Conky color codes")

gflags.DEFINE_string("color_owner", "cyan", "Color for owned calendars")
gflags.DEFINE_string("color_writer", "green", "Color for writable calendars")
gflags.DEFINE_string("color_reader", "magenta",
                     "Color for read-only calendars")
gflags.DEFINE_string("color_freebusy", "default",
                     "Color for free/busy calendars")
gflags.DEFINE_string("color_date", "yellow", "Color for the date")
gflags.DEFINE_string("color_now_marker", "brightred",
                     "Color for the now marker")
gflags.DEFINE_string("color_border", "white", "Color of line borders")

gflags.DEFINE_string("locale", None, "System locale")

gflags.DEFINE_multistring("reminder", [],
                          "Reminders in the form 'TIME METH' or 'TIME'.  TIME "
                          "is a number which may be followed by an optional "
                          "'w', 'd', 'h', or 'm' (meaning weeks, days, hours, "
                          "minutes) and default to minutes.  METH is a string "
                          "'popup', 'email', or 'sms' and defaults to popup.")
gflags.DEFINE_string("title", None, "Event title")
gflags.DEFINE_string("where", None, "Event location")
gflags.DEFINE_string("when", None, "Event time")
gflags.DEFINE_integer("duration", None,
                      "Event duration in minutes or days if --allday is given.")
gflags.DEFINE_string("description", None, "Event description")
gflags.DEFINE_bool("allday", False,
                   "If --allday is given, the event will be an all-day event "
                   "(possibly multi-day if --duration is greater than 1). The "
                   "time part of the --when will be ignored.")
gflags.DEFINE_bool("prompt", True,
                   "Prompt for missing data when adding events")
gflags.DEFINE_bool("default_reminders", True,
                   "If no --reminder is given, use the defaults.  If this is "
                   "false, do not create any reminders.")

gflags.DEFINE_bool("iamaexpert", False, "Probably not")
gflags.DEFINE_bool("refresh", False, "Delete and refresh cached data")
gflags.DEFINE_bool("cache", True, "Execute command without using cache")

gflags.DEFINE_bool("verbose", False, "Be verbose on imports",
                   short_name="v")
gflags.DEFINE_bool("dump", False, "Print events and don't import",
                   short_name="d")

gflags.DEFINE_bool("use_reminders", False, "Honour the remind time when running remind command")

gflags.RegisterValidator("details",
                         lambda value: all(x in ["all", "calendar",
                                                 "location", "length",
                                                 "reminders", "description",
                                                 "longurl", "shorturl", "url",
                                                 "attendees", "email"]
                                           for x in value))
gflags.RegisterValidator("reminder",
                         lambda value: all(ParseReminder(x) for x in value))
gflags.RegisterValidator("color_owner",
                         lambda value: GetColor(value) is not None)
gflags.RegisterValidator("color_writer",
                         lambda value: GetColor(value) is not None)
gflags.RegisterValidator("color_reader",
                         lambda value: GetColor(value) is not None)
gflags.RegisterValidator("color_freebusy",
                         lambda value: GetColor(value) is not None)
gflags.RegisterValidator("color_date",
                         lambda value: GetColor(value) is not None)
gflags.RegisterValidator("color_now_marker",
                         lambda value: GetColor(value) is not None)
gflags.RegisterValidator("color_border",
                         lambda value: GetColor(value) is not None)

gflags.ADOPT_module_key_flags(gflags)


def BowChickaWowWow():
    try:
        argv = sys.argv
        if os.path.exists(os.path.expanduser('~/.gcalclirc')):
            # We want .gcalclirc to be sourced before any other --flagfile
            # params since we may be told to use a specific config folder, we
            # need to store generated argv in temp variable
            tmpArgv = [argv[0], "--flagfile=~/.gcalclirc"] + argv[1:]
        else:
            tmpArgv = argv
        args = FLAGS(tmpArgv)
    except gflags.FlagsError, e:
        PrintErrMsg(str(e))
        Usage(True)
        sys.exit(1)

    if FLAGS.configFolder:
        if not os.path.exists(os.path.expanduser(FLAGS.configFolder)):
            os.makedirs(os.path.expanduser(FLAGS.configFolder))
        if os.path.exists(os.path.expanduser("%s/gcalclirc" %
                                             FLAGS.configFolder)):
            if not FLAGS.includeRc:
                tmpArgv = argv + ["--flagfile=%s/gcalclirc" %
                                  FLAGS.configFolder, ]
            else:
                tmpArgv += ["--flagfile=%s/gcalclirc" % FLAGS.configFolder, ]

        FLAGS.Reset()
        args = FLAGS(tmpArgv)

    argv = tmpArgv

    if FLAGS.version:
        Version()

    if FLAGS.help:
        Usage(True)
        sys.exit()

    if FLAGS.helpshort:
        Usage()
        sys.exit()

    if not FLAGS.color:
        CLR.useColor = False

    if not FLAGS.lineart:
        ART.useArt = False

    if FLAGS.conky:
        SetConkyColors()

    if FLAGS.locale:
        try:
            locale.setlocale(locale.LC_ALL, FLAGS.locale)
        except Exception, e:
            PrintErrMsg("Error: " + str(e) + "!\n"
                        "Check supported locales of your system.\n")
            sys.exit(1)

    # pop executable off the stack
    args = args[1:]
    if len(args) == 0:
        PrintErrMsg('Error: no command\n')
        sys.exit(1)

    # No sense instaniating gcalcli for nothing
    if not args[0] in ['list', 'search', 'agenda', 'calw', 'calm', 'quick',
                       'add', 'delete', 'edit', 'remind', 'import', 'help']:
        PrintErrMsg('Error: %s is an invalid command' % args[0])
        sys.exit(1)

    # all other commands require gcalcli be brought up
    if args[0] == 'help':
        Usage()
        sys.exit(0)

    if len(FLAGS.calendar) == 0:
        FLAGS.calendar = FLAGS.defaultCalendar

    calNames = []
    calNameColors = []
    calColors = GetCalColors(FLAGS.calendar)
    calNamesFiltered = []
    for calName in FLAGS.calendar:
        calNameSimple = calName.split("#")[0]
        calNamesFiltered.append(stringToUnicode(calNameSimple))
        calNameColors.append(calColors[calNameSimple])
    calNames = calNamesFiltered

    if 'all' in FLAGS.details or FLAGS.detail_all:
        if not FLAGS['detail_calendar'].present:
            FLAGS['detail_calendar'].value = True
        if not FLAGS['detail_location'].present:
            FLAGS['detail_location'].value = True
        if not FLAGS['detail_length'].present:
            FLAGS['detail_length'].value = True
        if not FLAGS['detail_reminders'].present:
            FLAGS['detail_reminders'].value = True
        if not FLAGS['detail_description'].present:
            FLAGS['detail_description'].value = True
        if not FLAGS['detail_url'].present:
            FLAGS['detail_url'].value = "long"
        if not FLAGS['detail_attendees'].present:
            FLAGS['detail_attendees'].value = True
        if not FLAGS['detail_email'].present:
            FLAGS['detail_email'].value = True
    else:
        if 'calendar' in FLAGS.details:
            FLAGS['detail_calendar'].value = True
        if 'location' in FLAGS.details:
            FLAGS['detail_location'].value = True
        if 'attendees' in FLAGS.details:
            FLAGS['detail_attendees'].value = True
        if 'length' in FLAGS.details:
            FLAGS['detail_length'].value = True
        if 'reminders' in FLAGS.details:
            FLAGS['detail_reminders'].value = True
        if 'description' in FLAGS.details:
            FLAGS['detail_description'].value = True
        if 'longurl' in FLAGS.details or 'url' in FLAGS.details:
            FLAGS['detail_url'].value = 'long'
        elif 'shorturl' in FLAGS.details:
            FLAGS['detail_url'].value = 'short'
        if 'attendees' in FLAGS.details:
            FLAGS['detail_attendees'].value = True
        if 'email' in FLAGS.details:
            FLAGS['detail_email'].value = True

    gcal = gcalcli(calNames=calNames,
                   calNameColors=calNameColors,
                   military=FLAGS.military,
                   detailCalendar=FLAGS.detail_calendar,
                   detailLocation=FLAGS.detail_location,
                   detailAttendees=FLAGS.detail_attendees,
                   detailLength=FLAGS.detail_length,
                   detailReminders=FLAGS.detail_reminders,
                   detailDescr=FLAGS.detail_description,
                   detailDescrWidth=FLAGS.detail_description_width,
                   detailUrl=FLAGS.detail_url,
                   detailEmail=FLAGS.detail_email,
                   ignoreStarted=not FLAGS.started,
                   calWidth=FLAGS.width,
                   calMonday=FLAGS.monday,
                   calOwnerColor=GetColor(FLAGS.color_owner),
                   calWriterColor=GetColor(FLAGS.color_writer),
                   calReaderColor=GetColor(FLAGS.color_reader),
                   calFreeBusyColor=GetColor(FLAGS.color_freebusy),
                   dateColor=GetColor(FLAGS.color_date),
                   nowMarkerColor=GetColor(FLAGS.color_now_marker),
                   borderColor=GetColor(FLAGS.color_border),
                   tsv=FLAGS.tsv,
                   refreshCache=FLAGS.refresh,
                   useCache=FLAGS.cache,
                   configFolder=FLAGS.configFolder,
                   client_id=FLAGS.client_id,
                   client_secret=FLAGS.client_secret,
                   defaultReminders=FLAGS.default_reminders,
                   allDay=FLAGS.allday
                   )

    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(stringToUnicode(args[1]))

        if not FLAGS.tsv:
            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 FLAGS.tsv:
            sys.stdout.write('\n')

    elif args[0] == 'calw':
        if not FLAGS.width:
            PrintErrMsg('Error: invalid width, don\'t be an idiot!\n')
            sys.exit(1)

        if len(args) >= 2:
            try:
                # Test to make sure args[1] is a number
                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 FLAGS.width:
            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.QuickAddEvent(stringToUnicode(args[1]),
                           reminder=FLAGS.reminder)

    elif (args[0] == 'add'):
        if FLAGS.prompt:
            if FLAGS.title is None:
                PrintMsg(CLR_MAG(), "Title: ")
                FLAGS.title = raw_input()
            if FLAGS.where is None:
                PrintMsg(CLR_MAG(), "Location: ")
                FLAGS.where = raw_input()
            if FLAGS.when is None:
                PrintMsg(CLR_MAG(), "When: ")
                FLAGS.when = raw_input()
            if FLAGS.duration is None:
                if FLAGS.allday:
                    PrintMsg(CLR_MAG(), "Duration (days): ")
                else:
                    PrintMsg(CLR_MAG(), "Duration (mins): ")
                FLAGS.duration = raw_input()
            if FLAGS.description is None:
                PrintMsg(CLR_MAG(), "Description: ")
                FLAGS.description = raw_input()
            if not FLAGS.reminder:
                while 1:
                    PrintMsg(CLR_MAG(),
                             "Enter a valid reminder or '.' to end: ")
                    r = raw_input()
                    if r == '.':
                        break
                    n, m = ParseReminder(str(r))
                    FLAGS.reminder.append(str(n) + ' ' + m)

        # calculate "when" time:
        eStart, eEnd = GetTimeFromStr(FLAGS.when, FLAGS.duration)

        gcal.AddEvent(FLAGS.title, FLAGS.where, eStart, eEnd,
                      FLAGS.description, FLAGS.reminder)

    elif args[0] == 'delete':
        eStart = None
        eEnd = None
        if len(args) < 2:
            PrintErrMsg('Error: invalid search string\n')
            sys.exit(1)
        elif len(args) == 4:  # search, start, end
            eStart = gcal.dateParser.fromString(args[2])
            eEnd = gcal.dateParser.fromString(args[3])
        elif len(args) == 3:  # search, start (default end)
            eStart = gcal.dateParser.fromString(args[2])

        # allow unicode strings for input
        gcal.DeleteEvents(stringToUnicode(args[1]),
                          FLAGS.iamaexpert, eStart, eEnd)

        sys.stdout.write('\n')

    elif args[0] == 'edit':
        if len(args) != 2:
            PrintErrMsg('Error: invalid search string\n')
            sys.exit(1)

        # allow unicode strings for input
        gcal.EditEvents(stringToUnicode(args[1]))

        sys.stdout.write('\n')

    elif args[0] == 'remind':
        if len(args) == 3:  # minutes and command
            gcal.Remind(int(args[1]), args[2], use_reminders=FLAGS.use_reminders)
        elif len(args) == 2:  # minutes
            gcal.Remind(int(args[1]), use_reminders=FLAGS.use_reminders)
        elif len(args) == 1:  # defaults
            gcal.Remind(use_reminders=FLAGS.use_reminders)
        else:
            PrintErrMsg('Error: invalid remind arguments\n')
            sys.exit(1)

    elif args[0] == 'import':
        if len(args) == 1:  # stdin
            gcal.ImportICS(FLAGS.verbose, FLAGS.dump, FLAGS.reminder)
        elif len(args) == 2:  # ics file
            gcal.ImportICS(FLAGS.verbose, FLAGS.dump, FLAGS.reminder, args[1])
        else:
            PrintErrMsg('Error: invalid import arguments\n')
            sys.exit(1)


def SIGINT_handler(signum, frame):
    PrintErrMsg('Signal caught, bye!\n')
    sys.exit(1)

signal.signal(signal.SIGINT, SIGINT_handler)

if __name__ == '__main__':
    BowChickaWowWow()

Offline

#42 2016-08-25 12:54:01

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

Head_on_a_Stick wrote:
ohnonot wrote:

how about keeping the work to one of these two threads?

Yes, please continue this discussion in this thread -- I have split the two calendar-related posts from the "Show your conky" thread and merged them here.

@OP: Sorry for the confusion.

It may be best to rename the thread to reflect the current problem in hand so that any solution can be found with a search engine.

Good idea. I've gone back and modified it, although it may need changing again. It's early.

With regard to the confusion, no worries. I'm confused most of the time, especially when it comes to code, scripts, and the like. smile

Offline

#43 2016-08-25 19:16:52

ohnonot
...again
Registered: 2015-09-29
Posts: 5,592

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

WaltH wrote:

Yes, that is what I should have done and will now go back and do. The changes aren't that hard to undo. If you want the actual gcalcli script, here it is (with my modifications - noted to the side):

#!/usr/bin/env python

s**t, it's python.
i'm not a complete python-analphabet but bash would have been easier for me...
anyhow, you have to find the bit where it parses the output so it is aligned with carefully counted spaces or tabs or however the script does it, and replace that bit with conky syntax ${goto nnn} variables. that way, conky will align the text nicely even if you use a variable width font.

Offline

#44 2016-08-25 21:50:30

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

I am also in contact with the developers through Github to see about how to make gcalcli distinguish different calendars with different colors. There is supposed to be a way to do, but I'm told there may be a bug in the config stuff. Once I get that figured out, then I will ask them if there is a way use a variable width font and have it look pretty. smile After that, I'll be back to trying to figure out how to make an rss feed of BBC News headlines display in Conky and have the text wrap if the headline is longer than 50 characters or so (including spaces). More to come.

Offline

#45 2016-08-26 21:28:36

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

Sector11 wrote:
WaltH wrote:

I felt a bit as if I had walked into a convention on another planet as the only English speaker. smile

That describes me to a T in so many ways.  mrpeachy and falldown worked magic in there.

WaltH wrote:

I will read through the man pages for Conky again. I'll also take a look at making and calling some external script as suggested by ohnonot and tknomanzr. I think with regard to Conky, my eyes are bigger that my stomach, as it were, in that I want to make it do more than I am capable at present of making it do.

Did a search of my /media/5/Conky/scripts directory for anything rss and found a couple of scripts:

curl-rss.sh

# RSS Feed Display Script by Hellf[i]re v0.1
#
# This script is designed for most any RSS Feed. As some feeds may not be
# completely compliant, it may need a bit of tweaking
#
# This script depends on curl.
# Gentoo: emerge -av net-misc/curl
# Debian: apt-get install curl
# Homepage: http://curl.haxx.se/
#
# Usage:
# .conkyrc: ${execi [time] /path/to/script/conky-rss.sh}
#
# Usage Example
# ${execi 300 /home/youruser/scripts/conky-rss.sh}

#RSS Setup
URI=http://www.nasa.gov/rss/image_of_the_day.rss #URI of RSS Feed
LINES=5   #Number of headlines

#Environment Setup
EXEC="/usr/bin/curl -s" #Path to curl

#Work Start
$EXEC $URI | grep title |\
sed -e :a -e 's/<[^>]*>//g;/</N' |\
sed -e 's/[ \t]*//' |\
sed -e 's/\(.*\)/ \1/' |\
sed -e 's/\.//' |\
sed -e 's/\"//' |\
sed -e 's/\"//' |\
head -n $(($LINES + 2)) |\
tail -n $(($LINES))

and: simple-rss-reader-v3.pl

#!/usr/bin/perl -w

############################
# Creator: Jeff Israel
#
# Script:	./simple-rss-reader-v3.pl
# Version: 	3.001
#
# Coded for for Wikihowto http://howto.wikia.com
#
# Description: 	This code downloads an RSS feed,
# 		extracts the <title> lines,
# 		cleans them up lines,
# 		prints the pretty lines
# 		exits on max-lines
# Usage:
# .conkyrc: ${execi [time] /path/to/script/simple-rss-reader-v3.pl}
#
# Usage Example
# ${execi 300 /path/to/script/simple-rss-reader-v3.pl}
#

use LWP::Simple;


############################
# Configs
#

#$rssPage = "http://tvrss.net/feed/combined/";
#$rssPage = "http://tvrss.net/feed/eztv/";
$rssPage = "http://www.nasa.gov/rss/image_of_the_day.rss";
$numLines = 10;
$maxTitleLenght = 35;

###########################
# Code
#

# Downloading RSS feed
my $pageCont = get($rssPage);

# Spliting the page to lines
@pageLines = split(/\n/,$pageCont);

# Parse each line, strip no-fun data, exit on max-lines
$numLines--; #correcting count for loop
$x = 0;
foreach $line (@pageLines) {
	if($line =~ /\<title\>/){ # Is a good line?
		#print "- $line\n";
		$lineCat = $line;
		$lineCat =~ s/.*\<title\>//;
		$lineCat =~ s/\<\/title\>.*//;
		$lineCat =~ s/\[.{4,25}\]$//; # strip no-fun data ( [from blaaa] )
		$lineCat = substr($lineCat, 0, $maxTitleLenght);
		print "- $lineCat \n";
		$x++;
	}
	if($x > $numLines) {
		last; #exit on max-lines
	}

}

#print $page;
#print "\nBy Bye\n";

to give you some samples.

Okay, since I am told there is a bug in gcalcli that doesn't seem to allow the user to differentiate multiple calendars with individual colors (the default is all calendars the user owns appear in one color, all calendars the user can write to in another color, all those the user can only read in a third color, and so on), I am revisiting this post.

With regard to the curl script, I installed curl, however, the script does not seem to work for me

With regard to the perl script, I have any number of files classified as programs with perl as part of the name, including perl, perl5.22.1, perl5.22-x86_64-linux-gnu, and dh_perl. I noticed, however, that your perl script did not call for the path to perl, unlike your curl script. With or without that path, however, I was not able to get the script to work for me.

Could either of these be because my rss feed link is an .xml rather than a .rss page as in your script examples?

Offline

#46 2016-08-27 06:09:50

ohnonot
...again
Registered: 2015-09-29
Posts: 5,592

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

the "curl script" is actually a shell script.
perl scripts, otoh, can be executed like e.g. bash scripts (note the first line of the file). once perl is installed, you shouldn't need to bother any further with it.
both scripts have to be saved to respective files and marked executable (chmod +x scriptfile).
does that solve the issue?

Last edited by ohnonot (2016-08-27 06:10:27)

Offline

#47 2016-08-27 14:19:56

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

ohnonot wrote:

the "curl script" is actually a shell script.
perl scripts, otoh, can be executed like e.g. bash scripts (note the first line of the file). once perl is installed, you shouldn't need to bother any further with it.
both scripts have to be saved to respective files and marked executable (chmod +x scriptfile).
does that solve the issue?

The simple answer is no. The longer answer is that nothing happens for me with the simple-rss-reader-v3 script. The conky-rss.sh script runs but only pulls "BBC News - World" and no headlines. Here is that script (I only changed the URI to reflect the link to the BBC News RSS feed):

# RSS Feed Display Script by Hellf[i]re v0.1
#
# This script is designed for most any RSS Feed. As some feeds may not be
# completely compliant, it may need a bit of tweaking
#
# This script depends on curl.
# Gentoo: emerge -av net-misc/curl
# Debian: apt-get install curl
# Homepage: http://curl.haxx.se/
#
# Usage:
# .conkyrc: ${execi [time] /path/to/script/conky-rss.sh}
#
# Usage Example
# ${execi 300 /home/youruser/scripts/conky-rss.sh}

#RSS Setup
URI=http://feeds.bbci.co.uk/news/world/rss.xml #URI of RSS Feed
LINES=5   #Number of headlines

#Environment Setup
EXEC="/usr/bin/curl -s" #Path to curl

#Work Start
$EXEC $URI | grep title |\
sed -e :a -e 's/<[^>]*>//g;/</N' |\
sed -e 's/[ \t]*//' |\
sed -e 's/\(.*\)/ \1/' |\
sed -e 's/\.//' |\
sed -e 's/\"//' |\
sed -e 's/\"//' |\
head -n $(($LINES + 2)) |\
tail -n $(($LINES))

Offline

#48 2016-08-27 14:54:37

damo
....moderator....
Registered: 2015-08-20
Posts: 6,734

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

^ So what are the error mesages when you run the conky or scripts in a terminal?

How about seeing what the output of the curl command is on its own. Maybe there is an issue with the parsing?

curl -v http://feeds.bbci.co.uk/news/world/rss.xml | grep title

NB Don't use the "-s" switch here - it means "silent". "-v" means verbose output.

As a general principle, always run scripts in a terminal when you are debugging. Otherwise you are thrashing about in the dark!

EDIT:
Here is what I get with a one-liner (without the sed spaghetti!)

damo@hydrogen ~ $ curl -s http://feeds.bbci.co.uk/news/world/rss.xml | grep "<title" | grep -o -P '(?<=CDATA\[).*(?=\]\])'| tail -n +2 | head -n 5
Italy earthquake: Mass funeral for 35 victims
Syrian war: Barrel bombs kill 15 in Aleppo, reports say
Donald Trump doctor admits writing health note in five minutes
France burkini ban: Mayors urged to heed court's ruling
China school pollution reports 'wrong' - investigation

Last edited by damo (2016-08-27 15:02:09)


Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt  «» BunsenLabs on DeviantArt

Offline

#49 2016-08-27 15:03:27

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

The simple-rss-reader-v3.pl script does not pull in any of the headlines from the rss feed, though it does get the spacing right. Here's a look at what I mean:
2yAByx8m.png
It puts dashes in but no headlines.

Offline

#50 2016-08-27 15:06:53

Sector11
Mod Squid Tpyo Knig
From: Upstairs
Registered: 2015-08-20
Posts: 8,030

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

Sorry I missed you post to me WaltH, as I said I don't use gcalcli - I just had those scripts from year ago.  You are running Peppermeint (Ubuntu) maybe Marks of GoogleCalendar scripts still works.  I'm still using a few of his scripts here in Debian so have a look.

Conky Companions

Scroll down to: conkygooglecalendar     2.16
The README:

Author: Mark Buck (Kaivalagi) <m_buck@hotmail.com>

PREREQUISITES
=============

It is expected that the user is already familiar with Conky.

Any conky specific help can be found by either visiting the conky website here:

    http://conky.sourceforge.net

Alternatively there is a large user base, various helpful posts are available
at http://www.ubuntuforums.org

For example, to see various incarnations of conky setups, or to ask technical
questions you could go here:
   
        Post your .conkyrc files w/ screenshots

        http://ubuntuforums.org/showthread.php?t=281865

If you are not familiar with conky there is help available here:

    HOW TO: A Beginners Guide to Setting up Conky

    http://ubuntuforums.org/showthread.php?t=867076


EXAMPLE USE
===========

In the /usr/share/conkygooglecalendar/example folder you'll find 2 files,
conkyrc and conkyGoogleCalendar.template

Conky can be run using these example files as follows:

    conky -c /usr/share/conkygooglecalendar/example/conkyrc &

HOWEVER the login details found in both files are invalid for obvious reasons.
I suggest looking at the files and making your own in your home folder based on
them. The call to conky would then need changing to suit the new path.

By running equivalent files with proper credentials you should see some output
at the top right of your screen.


COMMAND OPTIONS
===============

A break down of all the options available are below. The same details can be
found by running conkyGoogleCalendar --help

Usage: conkyGoogleCalendar [options]
Options:
  -h, --help            show this help message and exit
  -u USERNAME, --username=USERNAME
                        Username for login into Google Calendar, this will
                        normally be your gmail account
  -p PASSWORD, --password=PASSWORD
                        Password to login with, if not set the username is
                        used to fetch a 'conky' password from the keyring
  -r TEXT, --requestCalendarNames=TEXT
                        Define a list of calendars to request event data for,
                        calendar names should be separated by semi-colons ";".
                        For example --requestCalendarNames="cal1;cal2;other
                        cal" If not set all calendar data will be returned.
  -d NUMBER, --daysahead=NUMBER
                        [default: 7] Define the number of days ahead you wish
                        to retrieve calendar entries for, starting from today.
  -s DATE, --startdate=DATE
                        Define the start date to retrieve calendar events. In
                        the form '2007-12-01'
  -e DATE, --enddate=DATE
                        Define the end date to retrieve calendar events, must
                        be supplied if --startdate supplied. In the form
                        '2007-12-01'
  -a, --allevents       Retrieve all calendar events
  -w TEXT, --wordsearch=TEXT
                        Define the text to search calendar entries with.
  -l NUMBER, --limit=NUMBER
                        [default: 0] Define the maximum number of calendar
                        events to display, zero means no limit.
  -t FILE, --template=FILE
                        Template file determining the format for each event.
                        Use the following placeholders: [title], [starttime],
                        [endtime], [completetime], [duration], [location],
                        [description], [who]. Ensure only one placeholder per
                        line, as the whole line is removed if no data for that
                        placeholder exists.
  -f "DATEFORMAT", --dateformat="DATEFORMAT"
                        If used this overrides the default date formatting.
                        The values to use are standard formatting strings e.g.
                        Weekday=%a, Day=%d, Month=%m, Year=%y. For an output
                        like "Thu 15/10/2008" you would require
                        --dateformat="%a %d/%m/%y", to have no date you would
                        require --dateformat=""
  -F "TIMEFORMAT", --timeformat="TIMEFORMAT"
                        If used this overrides the default time formatting.
                        The values to use are standard formatting strings e.g.
                        Hours (12hr)=%l, Hours (24hr)=%H, Minutes=%M,
                        Seconds=%S, AM/PM=%P. For an output like "05:22 PM"
                        you would require --timeformat="%l:%M %P",
                        --timeformat="" is not supported, default locale
                        settings are used
  -i NUMBER, --indent=NUMBER
                        [default: 0] Define the number of spaces to indent the
                        output (excludes template based output)
  -m NUMBER, --maxwidth=NUMBER
                        [default: 40] Define the number of characters to
                        output per line
  -n, --nowho           Hides who is attending the events (excludes template
                        based output)
  -c NUMBER, --connectiontimeout=NUMBER
                        [default: 30] Define the number of seconds before a
                        connection timeout can occur.
  -v, --verbose         Request verbose output, no a good idea when running
                        through conky!
  -V, --version         Displays the version of the script.
  --errorlogfile=FILE   If a filepath is set, the script appends errors to the
                        filepath.
  --infologfile=FILE    If a filepath is set, the script appends info to the
                        filepath.

TEMPLATE FILES
==============

A template file is included in the example files and there are also details on
the template option in the command options listed above.

Note that you are able to combine standard font output with other fonts in a
single template, but must use either execp or execpi conky commands to do so.

Please take a look at the example template provided. You will see that it uses
text inside a "<" and a ">" as markers, these must be exact for the replacment
event text to be output. Anything outside of these markers will be output as
it looks in the template.

Note that it is advisable to only use one placeholder per line, as the whole
line in the template is removed from output if no data for that placeholder
exists.

FURTHER HELP
============

If you have an issue and are not sure, try running the same command in the
terminal window and add the option --verbose, you should then see lots of
information about what the script is doing, any warnings or errors should also
be displayed.

If after doing the above you are still stuck, further help can be found by
visiting this thread on ubuntuforums.org:

    Conky Google Calendar Python Script
   
    http://ubuntuforums.org/showthread.php?t=837385

Note that it is best to post --verbose output of your script call, as well as
the conkyrc you are using. This way the issue can be understood quickly and
easily.

ENJOY smile


Debian 12 Beardog, SoxDog and still a Conky 1.9er

Offline

#51 2016-08-27 18:36:18

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

Sector 11 - I have gcalcli pulling into my Conky fine for now. I found a fixed-width font that looks a bit like the other fonts I use, and by tweaking the SetConkyColors section in gcalcli and then creating a .gcalclirc file that uses the original colors, I have been able to make it so the various calendars are distinguished from one another, which was my main concern. The formatting so I can use variable-width fonts is a matter for another day, I think.

With regard to the script, I visited the Ubuntu forums and got the impression, anyway, that the script might no longer work. It also seemed there had been no discussion of it or about it in some time.

Offline

#52 2016-08-27 18:59:01

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

damo wrote:

^ So what are the error mesages when you run the conky or scripts in a terminal?

How about seeing what the output of the curl command is on its own. Maybe there is an issue with the parsing?

curl -v http://feeds.bbci.co.uk/news/world/rss.xml | grep title

NB Don't use the "-s" switch here - it means "silent". "-v" means verbose output.

As a general principle, always run scripts in a terminal when you are debugging. Otherwise you are thrashing about in the dark!

EDIT:
Here is what I get with a one-liner (without the sed spaghetti!)

damo@hydrogen ~ $ curl -s http://feeds.bbci.co.uk/news/world/rss.xml | grep "<title" | grep -o -P '(?<=CDATA\[).*(?=\]\])'| tail -n +2 | head -n 5
Italy earthquake: Mass funeral for 35 victims
Syrian war: Barrel bombs kill 15 in Aleppo, reports say
Donald Trump doctor admits writing health note in five minutes
France burkini ban: Mayors urged to heed court's ruling
China school pollution reports 'wrong' - investigation

When I run the simple-rss-reader-v3 script from the terminal, I get the following

Name "main::maxTitleLength" used only once: possible typo at simple-rss-reader-v3.pl line 33.
Name "main::maxTitleLenght" used only once: possible typo at simple-rss-reader-v3.pl line 55.
Use of uninitialized value $maxTitleLenght in substr at simple-rss-reader-v3.pl line 55.
-  
Use of uninitialized value $maxTitleLenght in substr at simple-rss-reader-v3.pl line 55.
-  
Use of uninitialized value $maxTitleLenght in substr at simple-rss-reader-v3.pl line 55.
-  
Use of uninitialized value $maxTitleLenght in substr at simple-rss-reader-v3.pl line 55.
-  
Use of uninitialized value $maxTitleLenght in substr at simple-rss-reader-v3.pl line 55.
-  

The error at line 33 was my mistake. I thought it was a spelling error and changed it. I changed it back, and the script now runs from the terminal. However, it still does not work within Conky.

When I run the conky-rss.sh script with the -s switch (or with no switch), I get the following:

<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>

With the -v swtich, I get this:

* Rebuilt URL to: conky-rss.sh/
*   Trying 198.105.244.23...
* Connected to conky-rss.sh (198.105.244.23) port 80 (#0)
> GET / HTTP/1.1
> Host: conky-rss.sh
> User-Agent: curl/7.47.0
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< Server: nginx
< Date: Sat, 27 Aug 2016 18:55:01 GMT
< Content-Type: text/html
< Transfer-Encoding: chunked
< Connection: close
< Location: http://localhost
< Expires: Sat, 27 Aug 2016 18:55:00 GMT
< Cache-Control: no-cache
< 
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Closing connection 0

When I plug this into my .conkyrc instead, I get my headlines (just without text wrap)

${rss http://feeds.bbci.co.uk/news/world/rss.xml 15 item_titles 5 2}

Last edited by WaltH (2016-08-27 18:59:31)

Offline

#53 2016-08-27 19:02:09

damo
....moderator....
Registered: 2015-08-20
Posts: 6,734

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

curl -s http://feeds.bbci.co.uk/news/world/rss.xml | grep "<title" | grep -o -P '(?<=CDATA\[).*(?=\]\])'| tail -n +2 | head -n 5

Still works fine for me hmm


Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt  «» BunsenLabs on DeviantArt

Offline

#54 2016-08-27 19:14:25

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

damo wrote:

^ So what are the error mesages when you run the conky or scripts in a terminal?

How about seeing what the output of the curl command is on its own. Maybe there is an issue with the parsing?

curl -v http://feeds.bbci.co.uk/news/world/rss.xml | grep title

NB Don't use the "-s" switch here - it means "silent". "-v" means verbose output.

As a general principle, always run scripts in a terminal when you are debugging. Otherwise you are thrashing about in the dark!

EDIT:
Here is what I get with a one-liner (without the sed spaghetti!)

damo@hydrogen ~ $ curl -s http://feeds.bbci.co.uk/news/world/rss.xml | grep "<title" | grep -o -P '(?<=CDATA\[).*(?=\]\])'| tail -n +2 | head -n 5
Italy earthquake: Mass funeral for 35 victims
Syrian war: Barrel bombs kill 15 in Aleppo, reports say
Donald Trump doctor admits writing health note in five minutes
France burkini ban: Mayors urged to heed court's ruling
China school pollution reports 'wrong' - investigation

Now I really am confused. The initial script had text at lines 33 and 55 that stated $maxTitleLenght (with length misspelled). I originally fixed the spelling at line 33 (missing line 55), and the script obviously did not run. I changed line 33 back and the script ran from the terminal but not in Conky. I corrected the spelling in both lines 33 and 55 and now the script sort of runs in Conky, but it looks like this:
XiyWvEtm.png
So it runs; it's just a bit messy. smile It also does not wrap the text of longer headlines (it just cuts them off), and maybe it's not designed to do that.

Offline

#55 2016-08-27 19:20:07

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

damo wrote:
curl -s http://feeds.bbci.co.uk/news/world/rss.xml | grep "<title" | grep -o -P '(?<=CDATA\[).*(?=\]\])'| tail -n +2 | head -n 5

Still works fine for me hmm

Okay, I've tried this command from within Conky, and it works, though it also does not seem to wrap. It simply cuts off letters, or in the case of current headlines one letter based on the width of my Conky screen. Thanks to everyone for their patience with my denseness.

Offline

#56 2016-08-27 19:21:08

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

twoion wrote:

1Using sed on XML is a bad idea as sed can't handle its complexity. Plug a XML parser instead:

# This will do the right thing bout make it impossible to seperate
# titles as it strips tags and doesn't insert newlines
curl http://feeds.bbci.co.uk/news/world/rss.xml | xmllint --nocdata --xpath "//rss/channel/item/title/text()" -

# So you can do this:
curl http://feeds.bbci.co.uk/news/world/rss.xml | xmllint --nocdata --xpath "/rss/channel/item/title" - | sed 's/<title>//g;s/<\/title>/\n/g'

Or just do the right thing and write a simple specialized tool. Such as this:

#!/usr/bin/env python
from __future__ import print_function
from argparse import ArgumentParser
import feedparser
import sys
ap = ArgumentParser()
ap.add_argument("-u", "--url",
    default="http://feeds.bbci.co.uk/news/world/rss.xml",
    help="Feed URL")
ap.add_argument("-l", "--limit",
        type=int,
        default=5,
        help="Print only the N most recent headlines")
opt = ap.parse_args()
try:
    feed = feedparser.parse(opt.url)
    for e in feed.entries[:opt.limit]:
        print(e.title)
except BaseException as err:
    print("ERROR:", err, file=sys.stderr)
    sys.exit(1)

Save as feedheadlines.py, then

# We are using this library
sudo apt-get install python-feedparser
chmod +x feedheadlines.py
# Get your BBC headlines
./feedheadlines.py

Should work with a lot more Atom/RSS feeds, such as the forum feed:

 $ python rssheadlines.py --url "https://forums.bunsenlabs.org/extern.php?action=feed&type=atom" --limit 20
Conky 1.10 ? (wrap rss feed data / incorporate gcalcli in user format)
Completely Off Topic Chat
What are you listening to right now?
August 2016 Screenshots
Cool Random Internet Stuff
bl-welcome fails if sudo rights are granted by /etc/suoders.d/ entries
The bunsenlabs 'exit' menu entry
BunsenLabs quotes
PowerShell Coming To Linux
A couple of new Menu>Preferences entries?
Android screenshots
[SOLVED] Chromium as dafault browser??
Show us your Mandelbulber render
[solved] banned from irc freenode chan?
What Are You Listening To? MetalHead Edition

I will explore this further tonight. Does this also wrap text if the headline is longer, or does it cut off letters (my current situation with everything I've tried)? Thanks.

Last edited by WaltH (2016-08-27 19:22:41)

Offline

#57 2016-08-27 20:04:15

damo
....moderator....
Registered: 2015-08-20
Posts: 6,734

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

The one-liner I gave just outputs each line of text. You will have to do further processing of it to wrap them.

PS There is no need to quote the whole of the post you are answering - just any relevant parts. It gets very unwieldly otherwise wink


Be Excellent to Each Other...
The Bunsenlabs Lithium Desktop » Here
FORUM RULES and posting guidelines «» Help page for forum post formatting
Artwork on DeviantArt  «» BunsenLabs on DeviantArt

Offline

#58 2016-08-28 00:03:35

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

damo wrote:

The one-liner I gave just outputs each line of text. You will have to do further processing of it to wrap them.

PS There is no need to quote the whole of the post you are answering - just any relevant parts. It gets very unwieldly otherwise wink

Yay! Got it! Thanks. A little research showed I needed to add the following to what you provided (after | head -n 5):

| fold -sw 55

Now to see what happens as the headlines update to see how it updates as time goes on and whether it breaks words in the middle, although it looks like it should be fine. Now I just need to fix the alignment of everything else. smile Thank you so much!

Last edited by WaltH (2016-08-28 00:03:57)

Offline

#59 2016-08-28 00:37:35

Bearded_Blunder
Dodging A Bullet
From: Seat: seat0; vc7
Registered: 2015-09-29
Posts: 1,146

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

damo wrote:

PS There is no need to quote the whole of the post you are answering - just any relevant parts. It gets very unwieldly otherwise wink

It is the default behaviour of the "Quote" link on the post though, perhaps the OP just used that?  Without realising you can delete non-relevant bits, or break it up by judicious manual insertion of suitable quote tags, or pasting bits into the box that pops up clicking Qoute on the reply form.  Took me a while to figure that out when I first started using forums.


Blessed is he who expecteth nothing, for he shall not be disappointed...
If there's an obscure or silly way to break it, but you don't know what.. Just ask me

Offline

#60 2016-08-28 00:45:48

WaltH
Member
Registered: 2016-08-21
Posts: 53

Re: (SOLVED) Conky 1.10 ? (wrap rss feed / incorporate gcalcli)

Bearded_Blunder wrote:

It is the default behaviour of the "Quote" link on the post though, perhaps the OP just used that?  Without realising you can delete non-relevant bits, or break it up by judicious manual insertion of suitable quote tags, or pasting bits into the box that pops up clicking Qoute on the reply form.  Took me a while to figure that out when I first started using forums.

I did just hit "Quote" though I was probably a bit lazy. To be honest, I was trying to post in a hurry and didn't look closely enough to decide which part of the message was relevant to my specific reply. I'll try to do better next time. smile

Offline

Board footer

Powered by FluxBB