123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. #!/bin/bash
  2. OVERDUER_FILTER=${OVERDUER_FILTER:-"( +OVERDUE or +WEEK )"}
  3. usage() {
  4. local self
  5. self=$(basename "$0")
  6. echo "usage: $self [--] [FILTER]" >&2
  7. echo "usage: $self --help" >&2
  8. exit 2
  9. }
  10. help() {
  11. local self
  12. self=$(basename "$0")
  13. echo "Usage:"
  14. echo " $self [-H] [-i] [--] [FILTER...]"
  15. echo ""
  16. echo "TaskWarrior wrapper to help manage (approaching!) due dates."
  17. echo ""
  18. echo "Options:"
  19. echo ""
  20. echo " -- Stop processing options; treat rest of arguments as"
  21. echo " TaskWarrior filter expression."
  22. echo ""
  23. echo " -n Do not modify any tasks; just show command that would do so."
  24. echo " Note: this does not turn off listing commands which, by design"
  25. echo " of TaskWarrior, can change numeric task IDs (not UUIDs)."
  26. echo ""
  27. echo " -H Do not include help messages in the template."
  28. echo ""
  29. echo " -i Include details of each task (\`task info\`) inside the"
  30. echo " template."
  31. echo ""
  32. echo "$self will use listing of TaskWarrior tasks past or close to their"
  33. echo "due date to create a template that will be automatically opened in"
  34. echo "your favorite editor. By editing the template and exiting the"
  35. echo "editor, you can assign new due dates to any of these tasks."
  36. echo "(Read instructions embedded inside the template for details.)"
  37. echo ""
  38. echo "If any tasks are modified, their new state is printed after the"
  39. echo "fact. (Hint: use \`task undo\` to revert last change)."
  40. echo ""
  41. echo "By default, filter '( +OVERDUE or +WEEK )' is used. This can be"
  42. echo "overriden by setting environment variable OVERDUER_FILTER or"
  43. echo "specifying filter expression directly on command line."
  44. echo ""
  45. echo "You need to have vipe (from moreutils) installed and set EDITOR"
  46. echo "environment variable, see vipe(1) for details."
  47. exit 0
  48. }
  49. warn() {
  50. local msg
  51. for msg in "$@";
  52. do
  53. echo "$msg" >& 2
  54. done
  55. }
  56. distill() {
  57. #
  58. # Remove comments and everything except due expression and UUID from stdin
  59. #
  60. local due
  61. local rest
  62. local uuid
  63. grep -v "^[[:space:]]*#" \
  64. | while read -r due rest;
  65. do
  66. test -n "$due" || continue
  67. uuid=${rest##* }
  68. test -n "$uuid" || continue
  69. echo "$due $uuid"
  70. done
  71. }
  72. maybe_do() {
  73. #
  74. # Maybe do it, maybe just say it
  75. #
  76. case $Dry in
  77. true) warn "$*" ;;
  78. false) "$@" ;;
  79. esac
  80. }
  81. apply() {
  82. #
  83. # Apply each pair of DUE UUID on stdin
  84. #
  85. local due
  86. local uuid
  87. local es=0
  88. while read -r due uuid;
  89. do
  90. task info "$uuid" >/dev/null || {
  91. warn "ignoring invalid UUID: $due for $uuid"
  92. es=3
  93. continue
  94. }
  95. task calc "$due" >/dev/null || {
  96. warn "ignoring invalid due date: $due for $uuid"
  97. es=3
  98. continue
  99. }
  100. maybe_do task "$uuid" modify "due:$due"
  101. task "$uuid" info
  102. done
  103. return $es
  104. }
  105. mkhelp1() {
  106. #
  107. # Print first part (before listing) of the help text
  108. #
  109. $AddHelp || return 0
  110. echo "#"
  111. echo "# Edit due dates below to your liking. The resulting format"
  112. echo "# must be:"
  113. echo "#"
  114. echo "# TIME [anything] UUID"
  115. echo "#"
  116. echo "# where TIME is a valid TaskWarrior time expression (hint: use"
  117. echo "# \`task calc TIME\`) and UUID, well, a valid task UUID. The"
  118. echo "# [anything] part is provided only for your convenience and will"
  119. echo "# be ignored."
  120. echo "#"
  121. echo "# What will also be ignored:"
  122. echo "#"
  123. echo "# * comments like this,"
  124. echo "# * empty lines,"
  125. echo "# * template lines that were left unchanged."
  126. echo "#"
  127. echo
  128. echo "#"
  129. echo "# CUR_DUE ID DESCRIPTION UUID"
  130. echo
  131. }
  132. mkhelp2() {
  133. #
  134. # Print second part (after the listing) of the help text
  135. #
  136. $AddHelp || return 0
  137. echo
  138. echo "#"
  139. echo "# Note that CUR_DUE is ONLY DATE, NOT TIME, while actual due"
  140. echo "# time may be set more precisely. If that matters, consult"
  141. echo "# full task info using syntax such as \`task ID info\` or -i."
  142. echo "# switch."
  143. echo "#"
  144. }
  145. mklisting() {
  146. #
  147. # Print task listing in form of "CUR_DUE If DESCRIPTION UUID"
  148. #
  149. task rc.detection:off rc.verbose:nothing \
  150. rc.defaultwidth:120 \
  151. rc.report.OVERDUER.columns:due,id,description,uuid \
  152. rc.report.OVERDUER.labels:DUE,ID,DESC,UUID \
  153. "${TaskFilter[@]}" \
  154. OVERDUER 2>/dev/null \
  155. | grep '^[^ ]' \
  156. | grep -v "^[[:space:]]*-" \
  157. | grep -v "^[[:space:]]*[A-Z]" \
  158. | sort
  159. }
  160. mkinfos() {
  161. #
  162. # Print extra info for each listed task; all commented out
  163. #
  164. local uuid
  165. $AddInfos || return 0
  166. echo ""
  167. echo ""
  168. echo "# #"
  169. echo "# Task details #"
  170. echo "# #"
  171. echo ""
  172. mklisting \
  173. | rev | cut -d' ' -f1 | rev \
  174. | while read -r uuid;
  175. do
  176. task "$uuid" info 2>/dev/null \
  177. | sed "s/^/# /"
  178. echo
  179. done
  180. }
  181. mktemplate() {
  182. #
  183. # Construct template text
  184. #
  185. mkhelp1
  186. mklisting
  187. mkhelp2
  188. mkinfos
  189. }
  190. main() {
  191. local Dry=false
  192. local tmp
  193. local es=0
  194. local AddHelp=true
  195. local AddInfos=false
  196. local TaskFilter=()
  197. while true; do case $1 in
  198. -n) Dry=true; shift ;;
  199. -H) AddHelp=false; shift ;;
  200. -i) AddInfos=true; shift ;;
  201. --) shift; break ;;
  202. --help) help ;;
  203. -*) usage ;;
  204. *) break ;;
  205. esac done
  206. which vipe >/dev/null || {
  207. warn "fatal: vipe is not installed"
  208. exit 3
  209. }
  210. TaskFilter=("$@")
  211. test -n "${TaskFilter[*]}" || TaskFilter=("$OVERDUER_FILTER")
  212. tmp=$(mktemp -d -t overduer.main.XXXXXXXX)
  213. mktemplate \
  214. | tee "$tmp/old" \
  215. | vipe > "$tmp/new"
  216. <"$tmp/old" distill | sort >"$tmp/old.distilled"
  217. <"$tmp/new" distill | sort >"$tmp/new.distilled"
  218. comm -13 "$tmp/old.distilled" "$tmp/new.distilled" > "$tmp/changes"
  219. if test -s "$tmp/changes";
  220. then
  221. <"$tmp/changes" apply; es=$?
  222. else
  223. warn "no changes detected; nothing to do"
  224. es=1
  225. fi
  226. rm -r "$tmp"
  227. return $es
  228. }
  229. main "$@"