summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md31
-rwxr-xr-xcalendar-cli.py86
2 files changed, 88 insertions, 29 deletions
diff --git a/README.md b/README.md
index 8003b20..5f16d67 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ GUIs and Web-UIs are nice for some purposes, but I really find the command line
* Minor stuff that are repeated often. Writing something like "todo add make a calendar-cli system" or "cal add 'tomorrow 15:40+2h' doctor appointment" is just very much faster than navigating into some web calendar interface and add an item there.
* Things that are outside the scope of the UI. Here is one of many tasks I'd like to do: "go through the work calendar, find all new calendar events that are outside office hours, check up with the personal calendar if there are potential conflicts, add some information at the personal calendar if appropriate", and vice versa - it has to be handled very manually if doing it through any normal calendar application as far as I know, but if having some simple CLI or python library I could easily make some interactive script that would help me doing the operation above.
-I've been looking a bit around, all I could find was cadaver and CalDAVClientLibrary. Both of those seems to be a bit shortcoming; they seem to miss the iCalendar parsing/generation. CalDAVClientLibrary is already a python library, and there also exist python libraries for iCalendar parsing/generation, so all that seems to be missing is the "glue" between those libraries.
+I've been looking a bit around, all I could find was cadaver and CalDAVClientLibrary. Both of those seems to be a bit shortcoming; they seem to miss the iCalendar parsing/generation. CalDAVClientLibrary is already a python library, and there also exist python libraries for iCalendar parsing/generation, so all that seems to be missing is the "glue" between those libraries. Or, eventually, not. After some problems I decided to ditch CalDAVClientLibrary.
Synopsis
--------
@@ -100,11 +100,10 @@ Remember to `chmod og-r ~/.config/calendar.conf` or `chmod 0600 ~/.config/calend
Add a calendar item "testevent" at 2013-10-01:
- ./calendar-cli.py calendar add 2013-10-01 testevent
+ ./calendar-cli.py calendar --calendar-url=http://calendar.bekkenstenveien53c.oslo.no/caldav.php/tobias/calendar/ add 2013-10-01 testevent
(assumes that `caldav-url`, `calldav-pass` and `caldav-user` has been added into configuration file. Those may also be added as command line options)
-
Objectives
----------
@@ -113,6 +112,31 @@ Objectives
* It should be possible to get out lists ("agenda") of calendar items and todo-items.
* Interface for copying calendar items between calendars, even between calendars on distinct caldav servers
+<<<<<<< HEAD
+Milestones
+----------
+
+* CLI-interface for creating ical calendar events (working as of version 0.01)
+* CalDAV login (working as of version 0.02)
+* Push calendar item into CalDAV server (working as of version 0.02, but both an URL for the caldav server and an URL for the actual calendar has to be given)
+* Config file with CalDAV connection details (working as of version 0.03)
+* Replace calendar-URL with calendar-path (working as of version 0.04)
+* Find default calendar-path (working as of version 0.04)
+* Show agenda
+* CLI-interface for creating ical todo events
+* Push todo item into CalDAV server
+
+Status
+------
+
+2013-09-15: Made a repository at github and wrote up this README.
+2013-09-24: version 0.01 - supports creating an ical-file based on command line parameters
+2013-09-28: version 0.02 - possible to add a calendar item to the caldav server
+2013-10-02: version 0.03 - support for configuration file
+2013-10-05: version 0.04 - no need to specify URL for the default calendar
+2013-12 - 2014-03: helped cyrilrbt on making a new release of the caldav library
+2014-03-07: version 0.05 - rewrote parts of the tool to using the caldav library. Nice!!!
+=======
Roadmap
-------
* Allow specification of event duration when adding events to calendar
@@ -134,3 +158,4 @@ History
* 2013-10-10: Attempts on implementing "agenda" stalled a bit due to problems with the library used. Considering to switch library.
* 2013-11-30: version 0.05 - added the calendar "addics" command for adding an ics file
* 2013-12-02: Some merging of work between the "agenda" branch and the master branch; causing a minor API change (new option --nocaldav has to be specified if running the utility without connecting to a caldav server)
+>>>>>>> master
diff --git a/calendar-cli.py b/calendar-cli.py
index f759ef5..8961356 100755
--- a/calendar-cli.py
+++ b/calendar-cli.py
@@ -1,22 +1,22 @@
-#!/usr/bin/python2.7
+#!/usr/bin/python2
## (the icalendar library is not ported to python3?)
import argparse
import urlparse
import pytz
+import tzlocal
from datetime import datetime, timedelta
import dateutil.parser
from icalendar import Calendar,Event
-from caldavclientlibrary.client.account import CalDAVAccount
-from caldavclientlibrary.protocol.url import URL
+import caldav
import uuid
import json
import os
import logging
import sys
-__version__ = "0.06"
+__version__ = "0.6.1"
__author__ = "Tobias Brox"
__author_short__ = "tobixen"
__copyright__ = "Copyright 2013, Tobias Brox"
@@ -34,27 +34,17 @@ def niy(*args, **kwargs):
def caldav_connect(args):
# Create the account
- splits = urlparse.urlsplit(args.caldav_url)
- ssl = (splits.scheme == "https")
- return CalDAVAccount(splits.netloc, ssl=ssl, user=args.caldav_user, pswd=args.caldav_pass, root=(splits.path or '/'), principal=None, logging=args.debug_logging)
+ return caldav.DAVClient(url=args.caldav_url, username=args.caldav_user, password=args.caldav_pass)
-def find_calendar_url(caldav_conn, args):
+def find_calendar(caldav_conn, args):
if args.calendar_url:
- splits = urlparse.urlsplit(args.calendar_url)
- if splits.path.startswith('/') or splits.scheme:
- ## assume fully qualified URL or absolute path
- url = args.calendar_url
+ if '/' in args.calendar_url:
+ return caldav.Calendar(client=caldav_conn, url=args.calendar_url)
else:
- ## assume relative path
- url = args.caldav_url + args.calendar_url
+ return caldav.Principal(caldav_conn).calendar(name=args.calendar_url)
else:
## Find default calendar
- url = caldav_conn.getPrincipal().listCalendars()[0].path
-
- ## Unique file name
- if not url.endswith('/'):
- url += '/'
- return url
+ return caldav.Principal(caldav_conn).calendars()[0]
def _calendar_addics(caldav_conn, ics, uid, args):
""""
@@ -70,8 +60,8 @@ def _calendar_addics(caldav_conn, ics, uid, args):
raise ValueError("Nothing to do/invalid option combination for 'calendar add'-mode; either both --icalendar and --nocaldav should be set, or none of them")
return
- url = URL(find_calendar_url(caldav_conn, args) + str(uid) + '.ics')
- caldav_conn.session.writeData(url, ics, 'text/calendar', method='PUT')
+ c = find_calendar(caldav_conn, args)
+ c.add_event(ics)
def calendar_addics(caldav_conn, args):
"""
@@ -117,9 +107,8 @@ def calendar_add(caldav_conn, args):
cal = Calendar()
cal.add('prodid', '-//{author_short}//{product}//{language}'.format(author_short=__author_short__, product=__product__, language=args.language))
cal.add('version', '2.0')
- if args.timezone:
- tz = pytz.timezone(args.timezone)
event = Event()
+ ## TODO: timezone
## read timestamps from arguments
dtstart = dateutil.parser.parse(args.event_time)
event.add('dtstart', dtstart)
@@ -134,6 +123,43 @@ def calendar_add(caldav_conn, args):
cal.add_component(event)
_calendar_addics(caldav_conn, cal.to_ical(), uid, args)
+def calendar_agenda(caldav_conn, args):
+ if args.nocaldav and args.icalendar:
+ niy(feature="Read events from stdin in ical format")
+
+ if args.nocaldav:
+ raise ValueError("Agenda with --nocaldav only makes sense together with --icalendar")
+
+ dtstart = dateutil.parser.parse(args.from_time)
+ if args.to_time:
+ dtend = dateutil.parser.parse(args.to_time)
+ else:
+ dtend = dtstart + timedelta(1,0)
+ ## TODO: time zone
+ ## No need with "expand" - as for now the method below throws away the expanded data :-( We get a list of URLs, and then we need to do a get on each and one of them ...
+ events_ = find_calendar(caldav_conn, args).date_search(dtstart, dtend)
+ events = []
+ if args.icalendar:
+ for ical in events_:
+ print ical.data
+ else:
+ ## flatten. A recurring event may be a list of events.
+ for event_cal in events_:
+ for event in event_cal.instance.components():
+ dtstart = event.dtstart.value
+ if not dtstart.tzinfo:
+ dtstart = args.timezone.localize(dtstart)
+ events.append({'dtstart': dtstart, 'instance': event})
+ events.sort(lambda a,b: cmp(a['dtstart'], b['dtstart']))
+ for event in events:
+ dtime = event['dtstart'].strftime("%F %H:%M")
+ summary = ""
+ for summary_attr in ('summary', 'location'):
+ if hasattr(event['instance'], summary_attr):
+ summary = getattr(event['instance'], summary_attr).value
+ break
+ print "%s %s" % (dtime, summary)
+
def main():
## This boilerplate pattern is from
## http://stackoverflow.com/questions/3609852
@@ -191,7 +217,7 @@ def main():
subparsers = parser.add_subparsers(title='command')
calendar_parser = subparsers.add_parser('calendar')
- calendar_parser.add_argument("--calendar-url", help="URL for calendar to be used (may be absolute or relative to caldav URL)")
+ calendar_parser.add_argument("--calendar-url", help="URL for calendar to be used (may be absolute or relative to caldav URL, or just the name of the calendar)")
calendar_subparsers = calendar_parser.add_subparsers(title='subcommand')
calendar_add_parser = calendar_subparsers.add_parser('add')
calendar_add_parser.add_argument('event_time', help="Timestamp and duration of the event. See the documentation for event_time specifications")
@@ -202,11 +228,19 @@ def main():
calendar_addics_parser.set_defaults(func=calendar_addics)
calendar_agenda_parser = calendar_subparsers.add_parser('agenda')
- calendar_agenda_parser.set_defaults(func=niy)
+ calendar_agenda_parser.add_argument('from_time', help="Fetch calendar events from this timestamp. See the documentation for time specifications")
+ calendar_agenda_parser.add_argument('--to-time', help="Fetch calendar until this timestamp")
+ calendar_agenda_parser.set_defaults(func=calendar_agenda)
+
todo_parser = subparsers.add_parser('todo')
todo_parser.set_defaults(func=niy)
args = parser.parse_args(remaining_argv)
+ if args.timezone:
+ args.timezone = pytz.timezone(args.timezone)
+ else:
+ args.timezone = tzlocal.get_localzone()
+
if not args.nocaldav:
caldav_conn = caldav_connect(args)