summaryrefslogtreecommitdiff
path: root/calendar-cli.py
diff options
context:
space:
mode:
authorTobias Brox <t@tobixen.no>2015-05-18 06:42:06 +0200
committerTobias Brox <t@tobixen.no>2015-05-18 06:42:06 +0200
commit86faaec1207d47bd2280cc3e0b3d082420db3293 (patch)
treece3a2580292be05a2333c9c8808908f195d35c0d /calendar-cli.py
parent6beb86ba9b3349ef244e0c50bcf1e84cd6cc2268 (diff)
parent05a30e0849ae3fe181fee0c49cede3c62d96ff4d (diff)
downloadcalendar-cli-86faaec1207d47bd2280cc3e0b3d082420db3293.zip
Merge branch 'v0.11.0.dev' into v0.11.0.dev.wip
Diffstat (limited to 'calendar-cli.py')
-rwxr-xr-xcalendar-cli.py76
1 files changed, 56 insertions, 20 deletions
diff --git a/calendar-cli.py b/calendar-cli.py
index f9c866d..f3dfad3 100755
--- a/calendar-cli.py
+++ b/calendar-cli.py
@@ -1,6 +1,8 @@
#!/usr/bin/python2
## (the icalendar library is not ported to python3?)
+## (also, the caldav library depends on the vobject library which is not officially ported to python3 as of 2015-05)
+## (vobject and icalendar are quite overlapping libraries)
import argparse
import pytz
@@ -17,7 +19,7 @@ import os
import logging
import sys
-__version__ = "0.10"
+__version__ = "0.11.0-dev"
__author__ = "Tobias Brox"
__author_short__ = "tobixen"
__copyright__ = "Copyright 2013, Tobias Brox"
@@ -28,14 +30,30 @@ __author_email__ = "t-calendar-cli@tobixen.no"
__status__ = "Development"
__product__ = "calendar-cli"
-def _force_datetime(t):
+def _force_datetime(t, args):
"""
date objects cannot be compared with timestamp objects, neither in python2 nor python3. Silly.
+ also, objects with time zone info cannot be compared with timestamps without time zone info.
+ and both datetime.now() and datetime.utcnow() seems to be without those bits. Silly.
"""
if type(t) == date:
- return datetime(t.year, t.month, t.day)
+ t = datetime(t.year, t.month, t.day)
+ if t.tzinfo is None:
+ return t.replace(tzinfo=_tz(args))
+ return t
+
+def _now():
+ """
+ python datetime is ... crap!
+ """
+ return datetime.utcnow().replace(tzinfo=pytz.utc)
+
+def _tz(args):
+ if args.timezone:
+ return pytz.timezone(args.timezone)
else:
- return t
+ return tzlocal.get_localzone()
+
## global constant
## (todo: this doesn't really work out that well, leap seconds/days are not considered, and we're missing the month unit)
@@ -225,7 +243,7 @@ def calendar_add(caldav_conn, args):
event.add('dtend', dtstart + timedelta(0,event_duration_secs))
## TODO: what does the cryptic comment here really mean, and why was the dtstamp commented out? dtstamp is required according to the RFC.
## not really correct, and it breaks i.e. with google calendar
- event.add('dtstamp', datetime.now())
+ event.add('dtstamp', _now())
## maybe we should generate some uid?
uid = uuid.uuid1()
event.add('uid', str(uid))
@@ -294,7 +312,7 @@ def todo_add(caldav_conn, args):
todo = Todo()
## TODO: what does the cryptic comment here really mean, and why was the dtstamp commented out? dtstamp is required according to the RFC.
## TODO: (cryptic old comment:) not really correct, and it breaks i.e. with google calendar
- todo.add('dtstamp', datetime.now())
+ todo.add('dtstamp', _now())
for setarg in ('due', 'dtstart'):
if getattr(args, 'set_'+setarg):
@@ -315,12 +333,21 @@ def todo_add(caldav_conn, args):
rt.value = str(uid)
t.save()
- for attr in vtodo_txt_one + vtodo_txt_many:
+ for attr in vtodo_txt_one:
if attr == 'summary':
continue
val = getattr(args, 'set_'+attr)
if val:
todo.add(attr, val)
+ ## TODO: this doesn't currently work quite the way we'd like it to
+ ## work (it adds to lines to the ical, and vobject cares only
+ ## about one of them), and if we do get it to work, we'd like to
+ ## refactor and get the same logic in the edit-function
+ for attr in vtodo_txt_many:
+ val = getattr(args, 'set_'+attr)
+ if val:
+ vals = val.split(',')
+ todo.add(attr, vals)
cal.add_component(todo)
_calendar_addics(caldav_conn, cal.to_ical(), uid, args)
print("Added todo item with uid=%s" % uid)
@@ -335,7 +362,7 @@ def calendar_agenda(caldav_conn, args):
if args.from_time:
dtstart = dateutil.parser.parse(args.from_time)
else:
- dtstart = datetime.now()
+ dtstart = _now()
if args.to_time:
dtend = dateutil.parser.parse(args.to_time)
elif args.agenda_mins:
@@ -361,11 +388,11 @@ def calendar_agenda(caldav_conn, args):
else:
raise Exception("Panic")
for event in events__:
- dtstart = event.dtstart.value if hasattr(event, 'dtstart') else datetime.now()
+ dtstart = event.dtstart.value if hasattr(event, 'dtstart') else _now()
if not isinstance(dtstart, datetime):
dtstart = datetime(dtstart.year, dtstart.month, dtstart.day)
if not dtstart.tzinfo:
- dtstart = args.timezone.localize(dtstart)
+ dtstart = _tz(args).localize(dtstart)
events.append({'dtstart': dtstart, 'instance': event})
events.sort(lambda a,b: cmp(a['dtstart'], b['dtstart']))
for event in events:
@@ -390,12 +417,18 @@ def todo_select(caldav_conn, args):
## TODO: we're fetching everything from the server, and then doing the filtering here. It would be better to let the server do the filtering, though that requires library modifications.
## TODO: current release of the caldav library doesn't support the multi-key sort_keys attribute. The try-except construct should be removed at some point in the future, when caldav 0.5 is released.
try:
- tasks = find_calendar(caldav_conn, args).todos(sort_keys=('dtstart', 'due', 'priority'))
+ tasks = find_calendar(caldav_conn, args).todos(sort_keys=('isnt_overdue', 'hasnt_started', 'due', 'dtstart', 'priority'))
except:
tasks = find_calendar(caldav_conn, args).todos()
for attr in vtodo_txt_one + vtodo_txt_many: ## TODO: now we have _exact_ match on items in the the array attributes, and substring match on items that cannot be duplicated. Does that make sense? Probably not.
if getattr(args, attr):
tasks = [x for x in tasks if hasattr(x.instance.vtodo, attr) and getattr(args, attr) in getattr(x.instance.vtodo, attr).value]
+ if getattr(args, 'no'+attr):
+ tasks = [x for x in tasks if not hasattr(x.instance.vtodo, attr)]
+ if args.overdue:
+ tasks = [x for x in tasks if hasattr(x.instance.vtodo, 'due') and _force_datetime(x.instance.vtodo.due.value, args) < _force_datetime(datetime.now(), args)]
+ if args.hide_future:
+ tasks = [x for x in tasks if not(hasattr(x.instance.vtodo, 'dtstart') and _force_datetime(x.instance.vtodo.dtstart.value, args) > _force_datetime(datetime.now(), args))]
if args.hide_parents or args.hide_children:
tasks_by_uid = {}
for task in tasks:
@@ -453,7 +486,7 @@ def todo_postpone(caldav_conn, args):
if args.until.startswith('+'):
rel_skew = timedelta(seconds=int(args.until[1:-1])*time_units[args.until[-1]])
elif args.until.startswith('in'):
- new_ts = datetime.now()+timedelta(seconds=int(args.until[2:-1])*time_units[args.until[-1]])
+ new_ts = _now()+timedelta(seconds=int(args.until[2:-1])*time_units[args.until[-1]])
else:
new_ts = dateutil.parser.parse(args.until)
if not new_ts.time():
@@ -504,9 +537,11 @@ def todo_list(caldav_conn, args):
for task in tasks:
t = {'instance': task}
t['dtstart'] = task.instance.vtodo.dtstart.value if hasattr(task.instance.vtodo,'dtstart') else date.today()
- t['dtstart_passed_mark'] = '!' if _force_datetime(t['dtstart']) <= datetime.now() else ' '
+ t['dtstart_passed_mark'] = '!' if _force_datetime(t['dtstart'], args) <= _now() else ' '
t['due'] = task.instance.vtodo.due.value if hasattr(task.instance.vtodo,'due') else date.today()+timedelta(365)
- t['due_passed_mark'] = '!' if _force_datetime(t['due']) < datetime.now() else ' '
+ t['due_passed_mark'] = '!' if _force_datetime(t['due'], args) < _now() else ' '
+ for timeattr in ('dtstart', 'due'):
+ t[timeattr] = t[timeattr].strftime(args.timestamp_format)
for summary_attr in ('summary', 'location', 'description', 'url', 'uid'):
if hasattr(task.instance.vtodo, summary_attr):
t['summary'] = getattr(task.instance.vtodo, summary_attr).value
@@ -632,10 +667,15 @@ def main():
todo_parser.add_argument('--todo-uid')
todo_parser.add_argument('--hide-parents', help='Hide the parent if you need to work on children tasks first (parent task depends on children tasks to be done first)', action='store_true')
todo_parser.add_argument('--hide-children', help='Hide the parent if you need to work on children tasks first (parent task depends on children tasks to be done first)', action='store_true')
+ todo_parser.add_argument('--overdue', help='Only show overdue tasks', action='store_true')
+ todo_parser.add_argument('--hide-future', help='Hide events with future dtstart', action='store_true')
for attr in vtodo_txt_one + vtodo_txt_many:
todo_parser.add_argument('--'+attr, help="for filtering tasks")
-
+
+ for attr in vtodo_txt_one + vtodo_txt_many:
+ todo_parser.add_argument('--no'+attr, help="for filtering tasks", action='store_true')
+
#todo_parser.add_argument('--priority', ....)
#todo_parser.add_argument('--sort-by', ....)
#todo_parser.add_argument('--due-before', ....)
@@ -654,6 +694,7 @@ def main():
todo_list_parser.add_argument('--todo-template', help="Template for printing out the event", default="{dtstart}{dtstart_passed_mark} {due}{due_passed_mark} {summary}")
todo_list_parser.add_argument('--default-due', help="Default number of days from a task is submitted until it's considered due", default=14)
todo_list_parser.add_argument('--list-categories', help="Instead of listing the todo-items, list the unique categories used", action='store_true')
+ todo_list_parser.add_argument('--timestamp-format', help="strftime-style format string for the output timestamps", default="%F (%a)")
todo_list_parser.set_defaults(func=todo_list)
todo_edit_parser = todo_subparsers.add_parser('edit')
@@ -709,11 +750,6 @@ def main():
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)