Skip to content

Commit

Permalink
more work on vtodo, adding support for editing and adding categories.…
Browse files Browse the repository at this point in the history
… plus some other minor fixes
  • Loading branch information
tobixen committed May 1, 2015
1 parent 52c3001 commit 5d879b6
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 19 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,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. Or, eventually, not. After some problems I decided to ditch CalDAVClientLibrary.
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, now I'm using the caldav library instead.

Synopsis
--------
Expand Down Expand Up @@ -65,7 +65,7 @@ All of those would eventually be supported in future versions if it's not too di
* Two ISO timestamps separated by a dash (-)
* ISO dates without times (default duration will be one day, for two dates full days up to and including the end date is counted)
* "tomorrow" instead of an ISO date
* weekday instead of an ISO date
* weekday instead of an ISO date (this seems supported already by dateutil.parser.parse)
* clock time without the date; event will be assumed to start within 24 hours.

Alternatively, endtime or duration can be given through options (not supported as of 0.9)
Expand Down
67 changes: 56 additions & 11 deletions calendar-cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ def _force_datetime(t):
'd': 86400, 'w': 604800, 'y': 31536000
}

vtodo_txt_one = ['location', 'description', 'geo', 'organizer', 'summary']
vtodo_txt_many = ['categories', 'comment', 'contact', 'resources']

def niy(*args, **kwargs):
if 'feature' in kwargs:
raise NotImplementedError("This feature is not implemented yet: %(feature)s" % kwargs)
Expand Down Expand Up @@ -352,15 +355,44 @@ def todo_select(caldav_conn, args):
if args.todo_uid:
tasks = find_calendar(caldav_conn, args).object_by_uid(args.todo_uid)
else:
## TODO: current release of the caldav library doesn't support the multi-key sort_keys attribute
#tasks = find_calendar(caldav_conn, args).todos(sort_keys=('dtstart', 'due', 'priority'))
tasks = find_calendar(caldav_conn, args).todos()
## 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'))
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 args.top+args.limit:
tasks = tasks[args.offset+args.offsetn:args.top+args.limit+args.offset+args.offsetn]
elif args.offset+args.offsetn:
tasks = tasks[args.offset+args.offsetn:]
return tasks

def todo_edit(caldav_conn, args):
tasks = todo_select(caldav_conn, args)
for task in tasks:
## TODO: code duplication - can we refactor this?
for attr in vtodo_txt_one:
if getattr(args, 'set_'+attr):
if not hasattr(task.instance.vtodo, attr):
task.instance.vtodo.add(attr)
getattr(task.instance.vtodo, attr).value = getattr(args, 'set_'+attr)
for attr in vtodo_txt_many:
if getattr(args, 'set_'+attr):
if not hasattr(task.instance.vtodo, attr):
task.instance.vtodo.add(attr)
getattr(task.instance.vtodo, attr).value = [ getattr(args, 'set_'+attr) ]
for attr in vtodo_txt_many:
if getattr(args, 'add_'+attr):
if not hasattr(task.instance.vtodo, attr):
task.instance.vtodo.add(attr)
getattr(task.instance.vtodo, attr).value = []
getattr(task.instance.vtodo, attr).value.append(getattr(args, 'add_'+attr))
task.save()


def todo_postpone(caldav_conn, args):
if args.nocaldav:
raise ValueError("No caldav connection, aborting")
Expand All @@ -378,11 +410,12 @@ def todo_postpone(caldav_conn, args):
tasks = todo_select(caldav_conn, args)
for task in tasks:
if new_ts:
if not hasattr(task.instance.vtodo, 'dtstart'):
task.instance.vtodo.add('dtstart')
task.instance.vtodo.dtstart.value = new_ts
attr = 'due' if args.due else 'dtstart'
if not hasattr(task.instance.vtodo, attr):
task.instance.vtodo.add(attr)
getattr(task.instance.vtodo, attr).value = new_ts
if rel_skew:
if hasattr(task.instance.vtodo, 'dtstart'):
if not args.due and hasattr(task.instance.vtodo, 'dtstart'):
task.instance.vtodo.dtstart.value += rel_skew
elif hasattr(task.instance.vtodo, 'due'):
task.instance.vtodo.due.value += rel_skew
Expand All @@ -407,13 +440,13 @@ def todo_list(caldav_conn, args):
tasks = todo_select(caldav_conn, args)
if args.icalendar:
for ical in tasks:
print(ical.data)
print(ical.data.encode('utf-8'))
else:
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['due'] = task.instance.vtodo.dtstart.value if hasattr(task.instance.vtodo,'dtstart') else date.today()+timedelta(365)
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 ' '
for summary_attr in ('summary', 'location', 'description', 'url', 'uid'):
if hasattr(task.instance.vtodo, summary_attr):
Expand Down Expand Up @@ -517,6 +550,10 @@ def main():
todo_parser.add_argument('--offsetn', type=int, default=0)
todo_parser.add_argument('--limit', type=int, default=0)
todo_parser.add_argument('--todo-uid')

for attr in vtodo_txt_one + vtodo_txt_many:
todo_parser.add_argument('--'+attr, help="for filtering - when adding new tasks, add this attribute")

#todo_parser.add_argument('--priority', ....)
#todo_parser.add_argument('--sort-by', ....)
#todo_parser.add_argument('--due-before', ....)
Expand All @@ -531,9 +568,17 @@ 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.set_defaults(func=todo_list)


todo_edit_parser = todo_subparsers.add_parser('edit')
for attr in vtodo_txt_one + vtodo_txt_many:
todo_edit_parser.add_argument('--set-'+attr, help="Set "+attr)
for attr in vtodo_txt_many:
todo_edit_parser.add_argument('--add-'+attr, help="Add an "+attr)
todo_edit_parser.set_defaults(func=todo_edit)

todo_postpone_parser = todo_subparsers.add_parser('postpone')
todo_postpone_parser.add_argument('until', help="either a new date or +interval to add some interval to the existing time, or a @+interval to set the time to a new time relative to the current time. interval is a number postfixed with a one character unit (any of smhdwy). If the todo-item has a dstart, this field will be modified, else the due timestamp will be modified. If both timestamps exists and dstart will be moved beyond the due time, the due time will be set to dtime+duration")
todo_postpone_parser.add_argument('until', help="either a new date or +interval to add some interval to the existing time, or i.e. 'in 3d' to set the time to a new time relative to the current time. interval is a number postfixed with a one character unit (any of smhdwy). If the todo-item has a dstart, this field will be modified, else the due timestamp will be modified. If both timestamps exists and dstart will be moved beyond the due time, the due time will be set to dtime.")
todo_postpone_parser.add_argument('--due', help="move the due, not the dtstart", action='store_true')
todo_postpone_parser.set_defaults(func=todo_postpone)

todo_complete_parser = todo_subparsers.add_parser('complete')
Expand Down
16 changes: 10 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
## http://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path.
## Since we depend on caldav which depends on vobject which currently
## doesn't support python3, it's not an issue right now.
import imp
my_script = imp.load_source('my_script', './calendar-cli.py')
## but it is an issue that the purpose of this script is to enable installation of dependencies,
## and if the dependencies doesn't exist, this import breaks! TODO ...
metadata = {}
for attribute in ('version', 'author', 'author_email', 'license'):
if hasattr(my_script, '__%s__' % attribute):
metadata[attribute] = getattr(my_script, '__%s__' % attribute)

import imp
try:
my_script = imp.load_source('my_script', './calendar-cli.py')
for attribute in ('version', 'author', 'author_email', 'license'):
if hasattr(my_script, '__%s__' % attribute):
metadata[attribute] = getattr(my_script, '__%s__' % attribute)
except:
pass

setup(
name='calendar-cli',
Expand Down

0 comments on commit 5d879b6

Please sign in to comment.