dotfiles/taskwarrior/hooks/on-modify-apply-recur_after.rb
2024-11-07 16:25:41 -07:00

63 lines
1.9 KiB
Ruby
Executable file

#!/usr/bin/env ruby
require 'json'
require 'time'
DAYS = 60 * 60 * 24
WEEKS = DAYS * 7
TASKWARRIOR_DATE_FORMAT = "%Y%m%dT060000Z"
# Find the -final- task state that's passed to us.
json_task = $stdin.map{ |line| line }.last
task = JSON.parse(json_task)
last_task_form = json_task
# Only modify tasks that are done, but have a recur.
# Emit all other tasks' final states unchanged.
unless task.has_key?('status') && task.has_key?('recur_after') && (task['status'] == 'completed')
puts(json_task)
exit 0
end
#
# We have an recur-after recurring task -- modify it!
#
unless task.has_key? 'due'
puts "ERROR: task #{task['id']} has an 'recur_after' recurrance, but no due date!"
exit -1
end
completed_date = Time.parse(task['end'])
previous_due_date = Time.parse(task['due'])
# Figure our our next recurrance based on the taskwarrior period/duration syntax.
recur_date = case task['recur_after']
when /P(\d+)D/ then completed_date + ($1.to_i * DAYS)
when /P(\d+)W/ then completed_date + ($1.to_i * WEEKS)
when /P(\d+)M/ then Time.new(completed_date.year, completed_date.month + $1.to_i, completed_date.day)
when /P(\d+)H/ then Time.new(completed_date.year + $1.to_i, completed_date.month, completed_date.day)
else
puts "ERROR: task #{task['id']} has unsupported 'recur_after' duration #{task['recur_after']}"
exit -1
end
# Mutate the task to be pending, still, but with new dates.
task['status'] = 'pending'
task['due'] = recur_date.strftime(TASKWARRIOR_DATE_FORMAT)
# If this task has ancillary dates, maintain the offset between them and the due date.
['wait', 'start'].each do |field|
if task.has_key?(field)
field_offset = previous_due_date - Time.parse(task[field])
new_field_date = Time.parse(task['due']) - field_offset
task[field] = new_field_date.strftime(TASKWARRIOR_DATE_FORMAT)
end
end
# Emit the task back to TaskWarrior.
puts task.to_json