From 833d424b3eabe39f60c753784c8f1e27a07d3879 Mon Sep 17 00:00:00 2001 From: Tobias Brox Date: Wed, 12 Oct 2022 12:38:47 +0200 Subject: completion of recurring tasks. work in progress. --- NEXT_LEVEL.md | 61 +++++++++++++++++++++++++++++++++++++++------------ TASK_MANAGEMENT.md | 12 ++++++---- cal.py | 33 +++++++++++++++++++++++----- calendar-cli.py | 9 ++++---- tests/tests_kal.sh | 64 ++++++++++++++++++++++++++++++++++++++++-------------- 5 files changed, 134 insertions(+), 45 deletions(-) diff --git a/NEXT_LEVEL.md b/NEXT_LEVEL.md index ccf957a..39ce123 100644 --- a/NEXT_LEVEL.md +++ b/NEXT_LEVEL.md @@ -1,6 +1,14 @@ +## Tasks vs events vs journals + +In my paradigm, a task is a planned activity that should be done, but not at a specific time (but possibly it should be done within a specific time interval). When it's done - or when it is deemed irrelevant to do it - it can be striken out from the list. An event is something that should be done at a relatively well-defined time. While you may come an hour early or late to the Saturday night party and still have a lot of fun - if you come at the wrong date, then there won't be a party. + +It's not always black and white - events may be task-alike (like an appointment that will have to be rescheduled if you miss it) and tasks may be event-alike (i.e. with a very hard due-time and that cannot be done long time before the due date). + +Both events and tasks generally contain infomration about future plans, journals contain information about the past. + ## Some potential requirements from a good calendaring system: -* 100% of all calendar users wants the possibility to "strike out" a thing from a calendar (I heard it at the CalendarFest event, so it must be true). +* 100% of all calendar users wants the possibility to "strike out" a thing from a calendar (I heard it at the CalendarFest event, so it must be true - though it does break with my paradigm). * It may be useful to take meeting notes directly from a calendar application (it was also said at the CalendarFest). @@ -10,15 +18,20 @@ * In my opinion (ref TASK_MANAGEMENT), it makes sense on a daily basis to take out relatively short sorted list of tasks, look through it and keep the list short by procrastinating the things that seems less urgent. I've been using the DTSTART attribute for such procrastination, but it feels a bit wrong. -* For collaboration, it's important to check when participants have avaialble time, as well as to be able to invite participants to calendar entries, and to be able to reply to invitations in such a manner that the event both appears on the personal calendar and that the organizer gets notified on whom will participate. +* For collaboration, it's important to check when participants have available time, as well as to be able to invite participants to calendar entries, and to be able to reply to invitations in such a manner that the event both appears on the personal calendar and that the organizer gets notified on whom will participate. ## Standards as they are now: -* Tasks (VTODOs) can be "striked out", but they don't stick very well to the calendar, and it cannot be used for tracking the time spent working on a task +The RFC specifies three different kind of calendar resource object types, it's the VJOURNAL, VTODO and VEVENT. From a technical point of view, the differences are mostly those: -* VJOURNAL entries on a calendar is meant exactly for meeting notes ... probably as well as recording things like who was really participating in an event, and how much time did they spend on it? (when checking up: not really, a journal entry cannot have a duration or an end timestamp) +* The VEVENT has a DTEND, the VTODO has a DUE, and the VJOURNAL ... has neither. +* The VEVENT is generally expected to have a DTSTART. The VJOURNAL is generally expected to have a DTSTART set to a date. A VTODO need not have a DTSTART. -* Tasks have a DTSTART and either a DUE or a DURATION; the latter two are interchangable, the standard defines that the DURATION is the difference between DTSTART and DUE. The standard is a bit unclear on exactly what those timestamps are to be used for. I assume the DUE is the due date where a task should be completed. This may be a very hard due date (i.e. the task "packing the suitcase" obviously needs to be done before the plane departure). It makes sense to let DURATION be the time estimate for the task, then DTSTART will be the latest possible start time if the task is to be completed before the DUE date. Obviously, if one expects to spend half an hour packing the suitcase and one needs to rush out the door at 19:30, one ought to start packing the suit case long before 19:30. This breaks with my usage up until now; I've used DTSTART as the earliest point of time I'm planning to consider to start working on the task (and sometimes this may also be a well-defined timestamp - the suitcase probably shouldn't be packed several days before departure). +Calendaring systems usually have very different user interfaces for tasks and events (and journals are generally not used). Generally, tasks can be "striked out", but they don't stick very well to the calendar. None of the three types are well-suited for tracking the time spent working on a task or attending to a meeting. + +VJOURNAL entries on a calendar is meant exactly for meeting notes ... probably as well as recording things like who was really participating in an event, unfortunately not possible to track how much time they spend on it (A journal entry cannot have a duration or an end timestamp). + +Tasks may have a DTSTART and either a DUE or a DURATION; the latter two are interchangable, the standard defines that the DURATION is the difference between DTSTART and DUE. The standard is a bit unclear on exactly what those timestamps are to be used for. I assume the DUE is the due date where a task should be completed. This may be a very hard due date (i.e. the task "packing the suitcase" obviously needs to be done before the plane departure). It makes sense to let DURATION be the time estimate for the task, then DTSTART will be the latest possible start time if the task is to be completed before the DUE date. Obviously, if one expects to spend half an hour packing the suitcase and one needs to rush out the door at 19:30, one ought to start packing the suit case long before 19:30. This breaks with my usage up until now; I've used DTSTART as the earliest point of time I'm planning to consider to start working on the task (and sometimes this may also be a well-defined timestamp - the suitcase probably shouldn't be packed several days before departure). * It is possible to specify that a task should be a recurring task, but there is no explicit support in the RFC of completing an occurrence. In the existing version of calendar-cli, a new "historic" task instance is created and marked complete, while dtstart is bumped in the "open" task. (there is an alternative interpretation of "recurring task", it could mean "let's work on project A every Tuesday after lunch, all until it's completed"). @@ -28,31 +41,51 @@ ## Suggestion for work flow and use (or abuse?) of the icalendar standard: +### Sticking a task to the calendar + +One may have an agenda with lots of events and some "unallocated" time in between, and one may want to try to plan getting specific tasks done at specific times. It may be nice to get this into the calendar. + +If a task has a DTSTART set, it may be interpreted as the time one actually plan to start working with the task - but it's non-ideal, for one thing most calendaring user interfaces will not "stick" the task to the calendar on that, and the DTSTART attribute may be used for other purposes. + +It may make sense to make a VTOOD stick to the calendar by making up a VEVENT and letting that VEVENT be a child of the VTODO. + +Similarly, for calendar items that "needs" to be "striken out" (say, some course you need to take - if you miss the scheduled course time then you need to take it at a later time), it may make sense to create a "parent task" for it. + +Perhaps even make it to a general rule that all activities should be registered on the calendar as a VTODO/VEVENT parent/child pair, where the VTODO contains time estimates and the VEVENT contains planned time for doing the VTODO. + ### Time tracking -When marking a task (VTODO) as completed, also make it possible to mark up how much time was spent on it (i.e. "2 hours"), optionally when it was done (default, worked on it until just now), optionally a description of what was done. Similarly, it should be possible to write up some meeting notes and record that one actually spent time being in a meeting. +When marking a task (VTODO) as completed, it ought to be possible to mark up how much time was spent on it (i.e. "2 hours"), optionally when it was done (default, worked on it until just now), optionally a description of what was done. Similarly, for a VEVENT it should be possible to write up i.e. meeting notes and record that one actually spent time being in a meeting. -VTODO tasks are non-ideal for holding information on time spent while doing the task. While DTSTART and DURATION, or DTSTART with COMPLETED can be used in the VTODO for marking out how much time is actually used, this will efficiently overwrite other information stored in those attributes. I.e., DURATION may be used for time estimate, DUE (which should always be the same as DTSTART+DURATION) may be used for indicating when the task needs to be done, this is information it may be important to keep. +VTODO tasks are non-ideal for holding information on time spent while doing the task. While DTSTART and DURATION, or DTSTART with COMPLETED can be used in the VTODO for marking out how much time is actually used, this will efficiently overwrite other information stored in those attributes. I.e., DURATION may be used for time estimate, DUE may be used for indicating when the task needs to be done, this is information it may be important to keep (but to make things more difficult, DUE and DURATION are mutually exlusive). -If one always ensures to "stick" tasks to the calendar, time tracking can be done as if it was a VEVENT. However, event objects are also not really designed for keeping time tracking information - there is no participant state for "participated" in the event, the nearest is "accepted". "Accepted" means "I was planning to attend to this event", it doesn't mean "I actually participated in this event". It could also cause extra noise if one is to actively reject a calendar event after the event happened. And the time spent on the event may be different than planned (i.e. a meeting dragging out in time or being cut short), it doesn't always make sense to edit the DTSTART/DTEND of a meeting to indicate how much time was actually spent on the meeting. +If one always ensures to "stick" tasks to the calendar, time tracking can be done in the DTSTART/DTEND of the VEVENT. However, event objects are also not really designed for keeping time tracking information. As said, the primary purpose is to contain information about the future - not the past. There is no participant state for "participated" in the event, the nearest is "accepted". "Accepted" means "I was planning to attend to this event", it doesn't mean "I actually participated in this event". It could also cause extra noise if one is to actively reject a calendar event after the event happened, as it may cause notifications to be sent to the organizer. To make it even more complicated, the time spent on the event may be different than planned (i.e. a meeting dragging out in time or being cut short), it doesn't always make sense to edit the DTSTART/DTEND of a meeting to indicate how much time was actually spent on the meeting. -A VJOURNAL entry is supposed to describe the past, and could be a good place to store such data. Unfortunately VJOURNAL entries cannot have DURATION nor DTEND (and it's recommended to put a date rather than a timestamp into DTSTART). Still, I propose the usage of a VJOURNAL entry in the calendar to indicate that something has happened (and hence that time was spent on this something). The VJOURNAL may be marked as a child of a task or an event, it means time has been spent on the activity according to the duration on the object. If the time actually spent deviates from the scheduled or estimated duration and if it's undesireable to edit the original object, then one can simply create a new event for tracking real time spent, and mark it up as a child of the original event or task. +A VJOURNAL entry is (the only entry) supposed to describe the past, and could be a good place to store such data. Unfortunately VJOURNAL entries cannot have DURATION nor DTEND (and it's recommended to put a date rather than a timestamp into DTSTART). -Overtime and billing information is considered site-specific and outside the scope - but eventually, one can use X-style attributes. +It is a great mess - the designers of the calendar standards absolutely didn't consider the need of tracking tim spent. + +My proposal is to let a VJOURNAL be a child of a VEVENT to mark that the VEVENT took place and that one participated in it. If things went as planned, it's straight forward. If things didn't go as planned, then ... either one may need to edit the VEVENT or create a separate VEVENT (which may be a child of the original VEVENT) containing the correct timestamps. If it was a VTODO and it wasn't "stuck" to the calendar, then it's trivial to make an after-the-fact VEVENT (just be careful that no calendar invites are sent out). + +Overtime and billing information is so far considered site-specific and outside the scope. ### Striking out something from the calendar -A calendar event could be "striked-out" if it has a child VJOURNAL entry. A task should be "striked-out" if it has a COMPLETED timestamp. +This sort of leaves us with two ways of "striking out" something. There is the traditional way of marking the task as COMPLETED. Now if the task is connected to a VEVENT, the calendar item should also be striken out. + +The second way is to make sure there is a VJOURNAL attached as a child of the VEVENT or a (grand)grandchild of the VTODO. + +Those two ways of striking out things have fundamentally different meanings. The first is to mark that a task is done and closed and does not need to be revisited. The second is to mark that work time was spent on the task or event. One would typically want to use both kind of "strikes". Only the first one if one is to mark that no significant work time was spent on the task (i.e. because the task has been cancelled, or because the work time spent has been accounted for somewhere else), and only the second one (and then create more events/journals later) if work time was spent but the task was not completed. ### Task management -* A VEVENT linked up as a child to a VTODO means we've (tried to) allocate some time for doing the VTODO (hence "sticking" the task to the calendar). If the task isn't marked completed by the end of the event, the calendar system should point it out. The user should then either reschedule, procrastinate, cancel or mark it as completed. +* A VEVENT linked up as a child to a VTODO means we've (tried to) allocate some time for doing the VTODO (hence "sticking" the task to the calendar). If the task isn't marked completed by the end of the event, the calendar system should point it out. The user should then either reschedule, procrastinate, cancel, mark it as completed, or mark it as partially done. * A VEVENT linked up as a parent to a VTODO means the VTODO needs to be completed before the event. Once the event has passed, the VTODO should probably be cancelled if it wasn't done yet. * A VTODO (A) linked up as a parent to another VTODO (B) means either that B is a subtask of A or that A depends on B. In either case, B needs to be completed before A can be completed. -* DURATION (efficiently meaning DTSTART, when DUE is set) should be used for time estimates (this breaks with my previous usage of DTSTART for prioritizing tasks). In the case of subtasks, DURATION in the VEVENT should only indicate the "independent" time usage (so the real time estimate for the full task is the sum of the estimate for all the subtasks). (And this is silly, since that means the Total duration including all children tasks should eventually be calculated and presented by the calendar application. +* DURATION (efficiently meaning DTSTART, when DUE is set) should be used for time estimates (this breaks with my previous usage of DTSTART as the earliest time I would expect starting to work on the task). In the case of child/parent tasks, DURATION should (probably?) only indicate the "independent" time usage - so the full estimate of the time consumption for the whole project is the sum of the duration of all the VTODOs in the project. This may be a bit silly, as it either means the DUE for the "root task" must be set almost at project start (though, it may make sense if planning things in a top-down fashion), or that the DTSTART for the "root task" must be set almost at the project end (may make sense when considering dependencies - the subtasks needs to be done first). * PRIORITY should indicate how important it is to do the task by the indicated DUE date/timestamp. If PRIORITY=1, then the task is extremely important AND the DUE is a hard deadline. PRIORITY=9 may mean either that DUE is a "fanciful wish" OR that the task should simply be cancelled if it's difficult to get it done prior to the DUE date. @@ -62,7 +95,7 @@ A calendar event could be "striked-out" if it has a child VJOURNAL entry. A tas * If the upcoming task list is too daunting, it should be possible to semiautomatically procrastinate (move the due) upcoming items based on their priority. -* Recurring tasks is still a potential problem ... given the idea of keeping historic data as VJOURNAL ... is it at all possible to link up a VJOURNAL as a single occurrence of a recurring task? +* Recurring tasks is still a potential problem. ## New calendar-cli interface diff --git a/TASK_MANAGEMENT.md b/TASK_MANAGEMENT.md index 725b2d3..1876aa9 100644 --- a/TASK_MANAGEMENT.md +++ b/TASK_MANAGEMENT.md @@ -75,14 +75,18 @@ Recurring tasks The standard allows for recurring tasks, but doesn't really flesh out what it means that a task is recurring - except that it should show up on date searches if any of the recurrances are within the date search range. Date searches for future recurrances of tasks is ... quite exotic, why would anyone want to do that? -There are two kind of recurrances: +From a "user perspective", I think there are two kind of recurrences: -* Specified intervals - say, the floor should be cleaned every week. You usually do it every Monday, but one week everything is so hectic that you postpone it all until late Sunday evening. It would be irrational to wash it again the next day. -* Fixed-time. If you actually get paid for washing the floor and you have a contract stating that you get paid a weekly sum for washing the floor weekly, then you'd probably want to wash the floor again on Monday, even if it has been done just recently. Or perhaps one of the children is having swimming at school every Tuesday, so sometime during Monday (with a hard due set to Tuesday early morning) a gym bag with swimwear and a fresh towel should be prepared for the child. +* Specified intervals - say, the floor should be cleaned every week. You usually do it every Monday, but one week everything is so hectic that you postpone it all until late Sunday evening. It would be irrational to wash it again the next day. And if you missed the due date with more than a week - then obviously the next recurrence is not "previous week". (Except, one may argue that the status of previous week should be set to "CANCELLED") +* Fixed-time. If you actually get paid for washing the floor and you have a contract stating that you get paid a weekly sum for washing the floor weekly, then you'd probably want to wash the floor again on Monday, even if it has been done just recently. Or perhaps one of the children is having swimming at school every Tuesday, so sometime during Monday (with a hard due set to Tuesday early morning) a gym bag with swimwear and a fresh towel should be prepared for the child. Or the yearly income tax statement, should be delivered before a hard due date. + +I choose to interpret a RRULE with BY*-attributes set (like BYDAY=MO) as a recurring task with "fixed" due times, while a RRULE without BY*-attributes should be considered as a "interval"-style of recurring task. There can be only one status and one complete-date for a vtodo, no matter if it's recurring or not. -Based on my interpretation of the standards, I've implemented logic in calendar-cli task completion code to duplicate the vtodo entry if it has an rrule; one vtodo ends up as completed, the other gets a new timestamp based on the rrule ("next after today". An rrule may be set up both with fixed-time (as in "every Monday, at 10:00") and with specified intervals ("weekly"), so if you complete the task sunday evening, it will be due again Monday if it's a fixed-time rule and Sunday evening if it's with specified intervals). The two rrules are linked togheter through the recurrance-id attribute. (perhaps this logic should be moved from calendar-cli to the caldav library). +Based on my interpretation of the standards, possibly the correct way to mark once recurrence of a recurring task as complete, is to use the RECURRENCE-ID parameter and make several instances of the same UID. However, based on my understanding of the RFC, the timestamps in a "recurrence set" is strictly defined by the RRULE and the original DTSTART. This does probably fit well with the fixed-time recurrences (at least if one markes a missed recurrence with CANCELLED), but it does not fit particularly well with interval-based recurrences. + +I tried implementing some logic like this in calendar-cli, and it was working on DAViCal. However, it was missing tests, and I realize that it's kind of broken. I'm now moving this logic to the caldav layer. I'm also creating a "safe" logic which will split the completed task into a completely separate task and editing/moving DTSTART/DUE on the recurring event. This may be the more practical solution (perhaps combined with having a common parent for all the recurring tasks). There is no support for rrules outside the task completion code, so as for now the rrule has to be put in through another caldav client tool, through the --pdb option or through manually editing ical code. I believe recurring tasks is an important functionality, so I will implement better support for this at some point. diff --git a/cal.py b/cal.py index 949f47d..bad85ad 100755 --- a/cal.py +++ b/cal.py @@ -47,7 +47,7 @@ list_type = list ## /usr/lib/*/site-packages/click/types.py on how to do this. ## TODO: maybe find those attributes through the icalendar library? icalendar.cal.singletons, icalendar.cal.multiple, etc -attr_txt_one = ['location', 'description', 'geo', 'organizer', 'summary', 'class'] +attr_txt_one = ['location', 'description', 'geo', 'organizer', 'summary', 'class', 'rrule'] attr_txt_many = ['category', 'comment', 'contact', 'resources', 'parent', 'child'] def parse_dt(input, return_type=None): @@ -365,9 +365,18 @@ def delete(ctx, multi_delete, **kwargs): @select.command() @click.option('--add-category', default=None, help="Delete multiple things without confirmation prompt", multiple=True) @click.option('--complete/--uncomplete', default=None, help="Mark task(s) as completed") +@click.option('--complete-recurrence-mode', default='safe', help="Completion of recurrent tasks, mode to use - can be 'safe', 'thisandfuture' or '' (see caldav library for details)") @_set_attr_options(verb='set') @click.pass_context -def edit(ctx, add_category=None, complete=None, **kwargs): +def edit(*largs, **kwargs): + return _edit(*largs, **kwargs) + +def _edit(ctx, add_category=None, complete=None, complete_recurrence_mode='safe', **kwargs): + """ + Edits a task/event/journal + """ + if 'recurrence_mode' in kwargs: + complete_recurrence_mode = kwargs.pop('recurrence_mode') _process_set_args(ctx, kwargs) for obj in ctx.obj['objs']: ie = obj.icalendar_instance.subcomponents[0] @@ -386,7 +395,7 @@ def edit(ctx, add_category=None, complete=None, **kwargs): cats.extend(add_category) ie.add('categories', cats) if complete: - obj.complete() + obj.complete(handle_rrule=complete_recurrence_mode, rrule_mode=complete_recurrence_mode) elif complete is False: obj.uncomplete() obj.save() @@ -394,8 +403,12 @@ def edit(ctx, add_category=None, complete=None, **kwargs): @select.command() @click.pass_context +@click.option('--recurrence-mode', default='safe', help="Completion of recurrent tasks, mode to use - can be 'safe', 'thisandfuture' or '' (see caldav library for details)") def complete(ctx, **kwargs): - raise NotImplementedError() + """ + Mark tasks as completed (alias for edit --complete) + """ + return _edit(ctx, complete=True, **kwargs) @select.command() @click.pass_context @@ -451,9 +464,17 @@ def ical(ctx, ical_data, ical_file): def _process_set_args(ctx, kwargs): ctx.obj['set_args'] = {} for x in kwargs: - if x == 'set_category' and kwargs[x] != (): + if kwargs[x] is None or kwargs[x]==(): + continue + if x == 'set_rrule': + rrule = {} + for split1 in kwargs[x].split(';'): + k,v = split1.split('=') + rrule[k] = v + ctx.obj['set_args']['rrule'] = rrule + elif x == 'set_category': ctx.obj['set_args']['categories'] = kwargs[x] - elif x.startswith('set_') and kwargs[x] is not None and kwargs[x] != (): + elif x.startswith('set_'): ctx.obj['set_args'][x[4:]] = kwargs[x] if 'summary' in kwargs: ctx.obj['set_args']['summary'] = ctx.obj['set_args'].get('summary', '') + kwargs['summary'] diff --git a/calendar-cli.py b/calendar-cli.py index 1891497..0877f0a 100755 --- a/calendar-cli.py +++ b/calendar-cli.py @@ -758,26 +758,25 @@ def todo_complete(caldav_conn, args): if hasattr(remaining_task.instance.vtodo, 'recurrence_id'): del remaining_task.instance.vtodo.recurrence_id remaining_task.instance.vtodo.add('recurrence-id') - remaining_task.instance.vtodo.recurrence_id.value = next ## TODO: should be same type as dtstart (date or datetime) remaining_task.instance.vtodo.dtstart.value = next ## TODO: should be same type as dtstart (date or datetime) remaining_task.instance.vtodo.recurrence_id.params['RANGE'] = [ 'THISANDFUTURE' ] remaining_task.instance.vtodo.rrule + count_search = re.search('COUNT=(\d+)', completed_task.instance.vtodo.rrule.value) + if count_search: + remaining_task.instance.vtodo.rrule.value = re.replace('COUNT=(\d+)', 'COUNT=%d' % int(count_search.group(1))-1) remaining_task.save() ## the completed task should have recurrence id set to current time ## count in rrule should decrease + completed_task.instance.vtodo.remove(completed_task.instance.vtodo.rrule) if hasattr(completed_task.instance.vtodo, 'recurrence_id'): del completed_task.instance.vtodo.recurrence_id completed_task.instance.vtodo.add('recurrence-id') completed_task.instance.vtodo.recurrence_id.value = datetime.now() completed_task.instance.vtodo.dtstart.value = datetime.now() - count_search = re.search('COUNT=(\d+)', completed_task.instance.vtodo.rrule.value) - if count_search: - completed_task.instance.vtodo.rrule.value = re.replace('COUNT=(\d+)', 'COUNT=%d' % int(count_search.group(1))-1) completed_task.complete() continue - task.complete() diff --git a/tests/tests_kal.sh b/tests/tests_kal.sh index 597fea3..d613a11 100755 --- a/tests/tests_kal.sh +++ b/tests/tests_kal.sh @@ -158,29 +158,17 @@ fi echo "## TODOS / TASK LISTS" echo "## Attempting to add a task with category 'scripttest'" -kal add todo --set-class=PUBLIC --set-category scripttest "edit this task" +kal add todo --set-class=PRIVATE --set-category scripttest "edit this task" uidtodo1=$(echo $output | perl -ne '/uid=(.*)$/ && print $1') kal add todo --set-class=CONFIDENTIAL --set-category scripttest "edit this task2" uidtodo2=$(echo $output | perl -ne '/uid=(.*)$/ && print $1') -kal add todo --set-class=PRIVATE "another task for testing sorting, offset and limit" +kal add todo --set-class=PUBLIC "another task for testing sorting, offset and limit" uidtodo3=$(echo $output | perl -ne '/uid=(.*)$/ && print $1') echo "## Listing out all tasks with category set to 'scripttest'" kal select --todo --category scripttest list [ $(echo "$output" | wc -l) == 2 ] || error "We found more or less or none of the two todo items we just added" -echo "## Editing the task" -kal select --todo --category scripttest edit --set-summary "editing" --add-category "scripttest2" - -echo "## Verifying that the edits got through" -kal select --todo --category scripttest list -[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just edited and nothing more" -kal select --todo --category scripttest2 list -[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just edited and nothing more" -kal select --todo --category scripttest3 list -[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just edited and nothing more" -kal select --todo --comment editing list -[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just edited and nothing more" echo "## Sort order and limit. CONFIDENTIAL class should come first. Only one task should be returned" kal select --todo --sort-key=CLASS --limit 1 list --template '{CLASS}' @@ -190,6 +178,10 @@ echo "## print-uid subcommand will print the uid of the first thing found" kal select --todo --sort-key=CLASS print-uid [ $output == $uidtodo2 ] || error "print-uid subcommand does not work" +kal select --todo --sort-key='class' --limit 2 list --template '{CLASS}' +[ $(echo "$output" | wc -l) == 2 ] && echo "## OK: limit=2 working" +echo "$output" | grep -q PRIVATE || error "Limit/sorting does not work as expected" + echo "## Offset. PRIVATE should come in the middle" kal select --todo --sort-key=class --limit 1 --offset 1 list --template '{CLASS}' echo "$output" | grep -q PRIVATE || error "Offset does not work as expected" @@ -204,9 +196,28 @@ echo "$output" | grep -q 'another task' || error "sort by template not working a kal select --todo --sort-key='{CATEGORIES.cats[0]:?zzz?}' --limit 1 list --template '{SUMMARY}' echo "$output" | grep -q 'another task' && error "sort by template not working as expected" -## TODO: add tests for limit!=1 and no limit +echo "## Utilizing two sort keys" +kal select --todo --sort-key='{CATEGORIES.cats[0]:?zzz?}' --sort-key=CLASS --limit 1 list --template '{CLASS}' +[ "$output" == "CONFIDENTIAL" ] || error "two sort keys didn't work as expected" +kal select --todo --sort-key='{CATEGORIES.cats[0]:?zzz?}' --sort-key=-CLASS --limit 1 list --template '{CLASS}' +[ "$output" == "PRIVATE" ] || error "two sort keys didn't work as expected" + +echo "## Editing the task" +kal select --todo --category scripttest edit --set-summary "editing" --add-category "scripttest2" + ## TODO: add tests for multiple sort keys +echo "## Verifying that the edits got through" +kal select --todo --category scripttest list +[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just edited and nothing more" +kal select --todo --category scripttest2 list +[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just edited and nothing more" +kal select --todo --category scripttest3 list +[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just edited and nothing more" +kal select --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" kal select --todo --category scripttest edit --complete kal select --todo --category scripttest list @@ -246,7 +257,28 @@ kal select --todo --skip-parents --category scripttest list kal select --todo --category scripttest list [ -z "$output" ] && echo "## OK: found no tasks now" -## TODO: test completion of recurring task +kal select --todo --uid $uidtodo1 --uid $uidtodo2 --uid $uidtodo3 delete --multi-delete + +echo "## test completion of recurring task" +kal add todo --set-category=scripttest --set-rrule="FREQ=YEARLY;COUNT=2" "this is a yearly task to be performed twice" +uidtodo=$(echo "$output" | perl -ne '/uid=(.*)$/ && print $1') +kal select --todo list --template='{UID}' +## since no time range is given, the task cannot be expanded +[ "$output" == "$uidtodo" ] || error "weird problem" +echo "## completing it should efficiently move the due one year into the future" +kal select --todo complete +kal select --todo list --template='{DUE.dt:%F}' +echo "$output" | grep -q "$(date -d '+1 year' +%F)" || error "completion of event with RRULE did not go so well" +echo "## since count is set to two, completing it for the second time should complete the whole thing" +kal select --todo complete +[ -z "$output" ] && echo "## OK: found no tasks now" +kal select --todo --uid $uidtodo delete + +echo "## test completion of recurring task with fixed occurance time" +kal add todo --set-category=scripttest --set-rrule='FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=1;BYHOUR=13;BYMINUTE=0;BYSECOND=0' "This task should be done next time 1st of January" +kal select --todo complete +kal select --todo list --template='{DUE.dt:%F}' +echo "$output" | grep -q "$(date -d '+1 year' +%Y)-01-01" || error "completion of event with RRULE did not go so well" echo "## some kal TESTS COMPLETED SUCCESSFULLY! YAY!" -- cgit v1.2.3