| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 | #!/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 "$@"
 |