#!/bin/bash OVERDUER_FILTER=${OVERDUER_FILTER:-"( +OVERDUE or +WEEK )"} usage() { local self self=$(basename "$0") echo "usage: $self [--] [FILTER]" >&2 echo "usage: $self --help" >&2 exit 2 } help() { local self self=$(basename "$0") echo "Usage:" echo " $self [-H] [-i] [--] [FILTER...]" echo "" echo "TaskWarrior wrapper to help manage (approaching!) due dates." echo "" echo "Options:" echo "" echo " -- Stop processing options; treat rest of arguments as" echo " TaskWarrior filter expression." echo "" echo " -n Do not modify any tasks; just show command that would do so." echo " Note: this does not turn off listing commands which, by design" echo " of TaskWarrior, can change numeric task IDs (not UUIDs)." echo "" echo " -H Do not include help messages in the template." echo "" echo " -i Include details of each task (\`task info\`) inside the" echo " template." echo "" echo "$self will use listing of TaskWarrior tasks past or close to their" echo "due date to create a template that will be automatically opened in" echo "your favorite editor. By editing the template and exiting the" echo "editor, you can assign new due dates to any of these tasks." echo "(Read instructions embedded inside the template for details.)" echo "" echo "If any tasks are modified, their new state is printed after the" echo "fact. (Hint: use \`task undo\` to revert last change)." echo "" echo "By default, filter '( +OVERDUE or +WEEK )' is used. This can be" echo "overriden by setting environment variable OVERDUER_FILTER or" echo "specifying filter expression directly on command line." echo "" echo "You need to have vipe (from moreutils) installed and set EDITOR" echo "environment variable, see vipe(1) for details." exit 0 } warn() { local msg for msg in "$@"; do echo "$msg" >& 2 done } distill() { # # Remove comments and everything except due expression and UUID from stdin # local due local rest local uuid grep -v "^[[:space:]]*#" \ | while read -r due rest; do test -n "$due" || continue uuid=${rest##* } test -n "$uuid" || continue echo "$due $uuid" done } maybe_do() { # # Maybe do it, maybe just say it # case $Dry in true) warn "$*" ;; false) "$@" ;; esac } apply() { # # Apply each pair of DUE UUID on stdin # local due local uuid local es=0 while read -r due uuid; do task info "$uuid" >/dev/null || { warn "ignoring invalid UUID: $due for $uuid" es=3 continue } task calc "$due" >/dev/null || { warn "ignoring invalid due date: $due for $uuid" es=3 continue } maybe_do task "$uuid" modify "due:$due" task "$uuid" info done return $es } mkhelp1() { # # Print first part (before listing) of the help text # $AddHelp || return 0 echo "#" echo "# Edit due dates below to your liking. The resulting format" echo "# must be:" echo "#" echo "# TIME [anything] UUID" echo "#" echo "# where TIME is a valid TaskWarrior time expression (hint: use" echo "# \`task calc TIME\`) and UUID, well, a valid task UUID. The" echo "# [anything] part is provided only for your convenience and will" echo "# be ignored." echo "#" echo "# What will also be ignored:" echo "#" echo "# * comments like this," echo "# * empty lines," echo "# * template lines that were left unchanged." echo "#" echo echo "#" echo "# CUR_DUE ID DESCRIPTION UUID" echo } mkhelp2() { # # Print second part (after the listing) of the help text # $AddHelp || return 0 echo echo "#" echo "# Note that CUR_DUE is ONLY DATE, NOT TIME, while actual due" echo "# time may be set more precisely. If that matters, consult" echo "# full task info using syntax such as \`task ID info\` or -i." echo "# switch." echo "#" } mklisting() { # # Print task listing in form of "CUR_DUE If DESCRIPTION UUID" # task rc.detection:off rc.verbose:nothing \ rc.defaultwidth:120 \ rc.report.OVERDUER.columns:due,id,description,uuid \ rc.report.OVERDUER.labels:DUE,ID,DESC,UUID \ "${TaskFilter[@]}" \ OVERDUER 2>/dev/null \ | grep '^[^ ]' \ | grep -v "^[[:space:]]*-" \ | grep -v "^[[:space:]]*[A-Z]" \ | sort } mkinfos() { # # Print extra info for each listed task; all commented out # local uuid $AddInfos || return 0 echo "" echo "" echo "# #" echo "# Task details #" echo "# #" echo "" mklisting \ | rev | cut -d' ' -f1 | rev \ | while read -r uuid; do task "$uuid" info 2>/dev/null \ | sed "s/^/# /" echo done } mktemplate() { # # Construct template text # mkhelp1 mklisting mkhelp2 mkinfos } main() { local Dry=false local tmp local es=0 local AddHelp=true local AddInfos=false local TaskFilter=() while true; do case $1 in -n) Dry=true; shift ;; -H) AddHelp=false; shift ;; -i) AddInfos=true; shift ;; --) shift; break ;; --help) help ;; -*) usage ;; *) break ;; esac done which vipe >/dev/null || { warn "fatal: vipe is not installed" exit 3 } TaskFilter=("$@") test -n "${TaskFilter[*]}" || TaskFilter=("$OVERDUER_FILTER") tmp=$(mktemp -d -t overduer.main.XXXXXXXX) mktemplate \ | tee "$tmp/old" \ | vipe > "$tmp/new" <"$tmp/old" distill | sort >"$tmp/old.distilled" <"$tmp/new" distill | sort >"$tmp/new.distilled" comm -13 "$tmp/old.distilled" "$tmp/new.distilled" > "$tmp/changes" if test -s "$tmp/changes"; then <"$tmp/changes" apply; es=$? else warn "no changes detected; nothing to do" es=1 fi rm -r "$tmp" return $es } main "$@"