From ae8979ed0701dd49a787f4edec939083f0f3db4e Mon Sep 17 00:00:00 2001 From: Tobias Brox Date: Sat, 2 May 2015 07:40:58 +0200 Subject: [PATCH] releasing version 0.10 --- README.md | 22 +++++++++---------- calendar-cli.py | 51 ++++++++++++++++++++++++++++++-------------- tests/script_test.sh | 29 +++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 6bb81ff..50c7ced 100644 --- a/README.md +++ b/README.md @@ -18,18 +18,18 @@ 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, now I'm using the caldav library instead. +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, and there are things that simply cannot be done through those tools. Synopsis -------- calendar-cli.py [global options] [command] [command options] [subcommand] [subcommand options] [subcommand arguments] ... -I'm intending to make it easier by allowing calendar-cli.py to be symlinked to the various commands. +I'm intending to make it easier by allowing calendar-cli.py to be symlinked to the various commands and also to allow the options to be placed wherever. ### Global options -Only long options will be available up until version 0.10; I don't +Only long options will be available in the early versions; I don't want to pollute the short option space before the CLI is reasonably well-defined. @@ -46,16 +46,14 @@ not be up-to-date and may contain features not implemented yet. ### Commands -* cal - access/modify a calendar - * subcommands: add, addics (for uploading events in ical format), agenda +* calendar - access/modify a calendar + * subcommands: add, addics (for uploading events in ical format), agenda, delete * todo - access/modify a todo-list - * subcommands: add, agenda - -(only "cal add" and "cal addics" works as of v0.06 / 2013-12-02) + * subcommands: add, list, edit, postpone, complete, delete ### Event time specification -Supported in v0.9: +Supported in v0.8: * anything recognized by dateutil.parser.parse() * An iso time stamp, followed with the duration, using either + or space as separator. Duration is a number postfixed by s for seconds, m for minutes, h for hours, d for days, w for weeks and y for years (i.e. 2013-09-10T13:37+30d) @@ -122,15 +120,17 @@ Changelog * 2015-02-15: version 0.7 - supports deletion of events, alternative templates for the event output and a small testing script * 2015-03-30: version 0.8 - has a interactive configuration mode for those not feeling comfortable with hand-crafting the config in json syntax * 2015-03-30: version 0.9 - now it's possible to set a duration when adding events to the calendar. +* 2015-05-02: version 0.10 - added support for todo subcommand Roadmap ------- +This is being moved out to the issues section in github. + * Allow pulling out an agenda for all calendars at once (though, with the current design it's so much easier to do it through a bash loop rather than in the python code, so this is postponed for a while) * Allow specification of event duration and/or event end time through options -* CLI-interface for creating ical todo events * Fix easy-to-use symlinks (or alternatively wrapper scripts) * Make some nosetests Donations --------- -Donations are not expected, but as long as this is a one-man hobby project at least it's not problematic to receive donations. Send bitcoins to 139xWFKwX9WejtRR1HP917qJGnRkZ6kn4M eventually. Donations may motivate me to work on specific feature requests or issues. +Donations are not expected, but as long as this is a one-man hobby project at least it's not problematic to receive donations. Send millibitcoins to 139xWFKwX9WejtRR1HP917qJGnRkZ6kn4M eventually. Donations may motivate me to work on specific feature requests or issues. diff --git a/calendar-cli.py b/calendar-cli.py index ee81f04..c0d474c 100755 --- a/calendar-cli.py +++ b/calendar-cli.py @@ -16,7 +16,7 @@ import logging import sys -__version__ = "0.7" +__version__ = "0.10" __author__ = "Tobias Brox" __author_short__ = "tobixen" __copyright__ = "Copyright 2013, Tobias Brox" @@ -228,7 +228,7 @@ def calendar_add(caldav_conn, args): ## maybe we should generate some uid? uid = uuid.uuid1() event.add('uid', str(uid)) - event.add('summary', ' '.join(args.description)) + event.add('summary', ' '.join(args.summary)) cal.add_component(event) _calendar_addics(caldav_conn, cal.to_ical(), uid, args) print("Added event with uid=%s" % uid) @@ -276,10 +276,10 @@ def todo_add(caldav_conn, args): cal.add('version', '2.0') 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: not really correct, and it breaks i.e. with google calendar + ## TODO: (cryptic old comment:) not really correct, and it breaks i.e. with google calendar todo.add('dtstamp', datetime.now()) - for arg in ('due', 'dtstart'): + for arg in ('set_due', 'set_dtstart'): if getattr(args, arg): if type(getattr(args, arg)) == str: val = dateutil.parser.parse(getattr(args, arg)) @@ -287,8 +287,14 @@ def todo_add(caldav_conn, args): val = getattr(args, arg) todo.add(arg, val) todo.add('uid', str(uid)) - todo.add('summary', args.description) + todo.add('summary', ' '.join(args.summaryline)) todo.add('status', 'NEEDS-ACTION') + for attr in vtodo_txt_one + vtodo_txt_many: + if attr == 'summary': + continue + val = getattr(args, 'set_'+attr) + if val: + todo.add(attr, val) cal.add_component(todo) _calendar_addics(caldav_conn, cal.to_ical(), uid, args) print("Added todo item with uid=%s" % uid) @@ -338,22 +344,22 @@ def calendar_agenda(caldav_conn, args): events.sort(lambda a,b: cmp(a['dtstart'], b['dtstart'])) for event in events: event['dstart'] = event['dtstart'].strftime(args.timestamp_format) - event['description'] = "(no description)" + event['summary'] = "(no description)" for summary_attr in ('summary', 'location', 'description'): if hasattr(event['instance'], summary_attr): - event['description'] = getattr(event['instance'], summary_attr).value + event['summary'] = getattr(event['instance'], summary_attr).value break event['uid'] = event['instance'].uid.value if hasattr(event['instance'], 'uid') else '' ## TODO: this will probably break and is probably moot on python3? - if hasattr(event['description'], 'encode'): - event['description'] = event['description'].encode('utf-8') + if hasattr(event['summary'], 'encode'): + event['summary'] = event['summary'].encode('utf-8') print(args.event_template.format(**event)) def todo_select(caldav_conn, args): if args.top+args.limit+args.offset+args.offsetn and args.todo_uid: raise ValueError("It doesn't make sense to combine --todo-uid with --top/--limit/--offset/--offsetn") if args.todo_uid: - tasks = find_calendar(caldav_conn, args).object_by_uid(args.todo_uid) + tasks = [ find_calendar(caldav_conn, args).object_by_uid(args.todo_uid) ] else: ## 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. @@ -465,6 +471,13 @@ def todo_complete(caldav_conn, args): for task in tasks: task.complete() +def todo_delete(caldav_conn, args): + if args.nocaldav: + raise ValueError("No caldav connection, aborting") + tasks = todo_select(caldav_conn, args) + for task in tasks: + task.delete() + def config_section(config, section='default'): if 'inherits' in config[section]: ret = config_section(config, config[section]['inherits']) @@ -552,16 +565,19 @@ def main(): 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('--'+attr, help="for filtering tasks") #todo_parser.add_argument('--priority', ....) #todo_parser.add_argument('--sort-by', ....) #todo_parser.add_argument('--due-before', ....) todo_subparsers = todo_parser.add_subparsers(title='tasks subcommand') todo_add_parser = todo_subparsers.add_parser('add') - todo_add_parser.add_argument('description', nargs='+') - todo_add_parser.add_argument('--due', default=date.today()+timedelta(7)) - todo_add_parser.add_argument('--dtstart', default=date.today()+timedelta(1)) + todo_add_parser.add_argument('summaryline', nargs='+') + todo_add_parser.add_argument('--set-due', default=date.today()+timedelta(7)) + todo_add_parser.add_argument('--set-dtstart', default=date.today()+timedelta(1)) + for attr in vtodo_txt_one + vtodo_txt_many: + if attr != 'summary': + todo_add_parser.add_argument('--set-'+attr, help="Set "+attr) todo_add_parser.set_defaults(func=todo_add) todo_list_parser = todo_subparsers.add_parser('list') @@ -583,12 +599,15 @@ def main(): todo_complete_parser = todo_subparsers.add_parser('complete') todo_complete_parser.set_defaults(func=todo_complete) + + todo_delete_parser = todo_subparsers.add_parser('delete') + todo_delete_parser.set_defaults(func=todo_delete) calendar_parser = subparsers.add_parser('calendar') calendar_subparsers = calendar_parser.add_subparsers(title='cal 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") - calendar_add_parser.add_argument('description', nargs='+') + calendar_add_parser.add_argument('summary', nargs='+') calendar_add_parser.set_defaults(func=calendar_add) calendar_addics_parser = calendar_subparsers.add_parser('addics') calendar_addics_parser.add_argument('--file', help="ICS file to upload", default='-') @@ -599,7 +618,7 @@ def main(): calendar_agenda_parser.add_argument('--to-time', help="Fetch calendar until this timestamp") calendar_agenda_parser.add_argument('--agenda-mins', help="Fetch calendar for so many minutes", type=int) calendar_agenda_parser.add_argument('--agenda-days', help="Fetch calendar for so many days", type=int, default=7) - calendar_agenda_parser.add_argument('--event-template', help="Template for printing out the event", default="{dstart} {description}") + calendar_agenda_parser.add_argument('--event-template', help="Template for printing out the event", default="{dstart} {summary}") calendar_agenda_parser.add_argument('--timestamp-format', help="strftime-style format string for the output timestamps", default="%F %H:%M (%a)") calendar_agenda_parser.set_defaults(func=calendar_agenda) diff --git a/tests/script_test.sh b/tests/script_test.sh index 001bb3a..375acdb 100755 --- a/tests/script_test.sh +++ b/tests/script_test.sh @@ -26,6 +26,8 @@ calendar_cli() { ## TESTING +## EVENTS + echo "## testing $calendar_cli" echo "## this is a very simple test script without advanced error handling" echo "## if this test script doesn't output 'all tests completed' in the end, something went wrong" @@ -57,4 +59,31 @@ echo "## Searching again for the deleted event" calendar_cli calendar agenda --from-time=2010-10-10 --agenda-days=1 echo $output | { grep -q 'testing testing' && echo "## FAIL: still found the event" ; } || echo "## OK: didn't find the event" +## TODOS / TASK LISTS + +echo "## Attempting to add a task with category 'scripttest'" +calendar_cli todo add --set-categories scripttest "edit this task" +uidtodo1=$(echo $output | perl -ne '/uid=(.*)$/ && print $1') + +echo "## Listing out all tasks with category set to 'scripttest'" +calendar_cli todo --categories scripttest list +[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just added and nothing more" + +echo "## Editing the task" +calendar_cli todo --categories scripttest edit --set-comment "editing" --add-categories "scripttest2" + +echo "## Verifying that the edits got through" +calendar_cli todo --categories scripttest list +[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just edited and nothing more" +calendar_cli todo --categories scripttest2 list +[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just edited and nothing more" +calendar_cli todo --comment editing list +[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just edited and nothing more" + +echo "## Complete the task" +calendar_cli todo --categories scripttest complete +calendar_cli todo --categories scripttest list +[ -z "$output" ] && echo "## OK: todo-item is done" +calendar_cli todo --todo-uid $todouid1 delete + echo "## all tests completed"