summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rwxr-xr-xcalendar-cli.py67
-rw-r--r--setup.py16
3 files changed, 68 insertions, 19 deletions
diff --git a/README.md b/README.md
index bcb4117..6bb81ff 100644
--- a/README.md
+++ b/README.md
@@ -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
--------
@@ -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)
diff --git a/calendar-cli.py b/calendar-cli.py
index da3d8c5..ee81f04 100755
--- a/calendar-cli.py
+++ b/calendar-cli.py
@@ -43,6 +43,9 @@ time_units = {
'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)
@@ -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")
@@ -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
@@ -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):
@@ -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', ....)
@@ -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')
diff --git a/setup.py b/setup.py
index b153da2..a3cebcb 100644
--- a/setup.py
+++ b/setup.py
@@ -9,13 +9,17 @@ from setuptools import setup, find_packages
## 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',