commit 3767d523ff012f697d21cc2795f82f42c5f0abad from: Sven M. Hallberg date: Fri Oct 04 11:58:05 2019 UTC initial record commit - /dev/null commit + 3767d523ff012f697d21cc2795f82f42c5f0abad blob - /dev/null blob + 30c3791c3dfc584693b6928f9f6191f5ee5859e6 (mode 644) --- /dev/null +++ painlist @@ -0,0 +1,256 @@ +#!/bin/sh +# simplistic pain list printer +# pesco 2016-2019 +# +# reads issue items on stdin, one per line, not indented. items are arbitrary +# text, rated by adding ">AxBxC<" anywhere on the line, where A,B,C,... are +# rating numbers in arbitrary categories. any number of categories can be used. +# +# if a pattern argument is given, it is interpreted as an extended regular +# expression to filter for. the -v option inverts the filter. +# +# empty lines and those containing only a shell-style comment (#) are ignored. +# in-line comments are not recognized; this allows the use of hash-tags. +# +# the rating scale is always 1-9; 9 should always be used for the highest level. +# coarser scales should be mapped to 1-9 by ignoring some levels; for instance, +# use 1,3,5,7,9 for a 5-level scale. it is recommended to document a clear +# meaning for each level to make rating easy and unambiguous. +# +# an item's pain value is the geometric mean of its ratings. +# +# the output sorts items by pain value. a solid line visualizes a configurable +# threshold (default -t 6). a dotted line shows the weighted median (half of +# total pain). higher-value items are shown in a brighter shade. +# +# +# == EXTRA FEATURES == +# +# it is suggested to use the following features only in specific circumstances +# as indicated. +# +# - text in square brackets [...] at the beginning of a line is highlighted in +# the output. this can be used for example to mark items with a planned time. +# +# - items can be marked for urgency after time T by adding a tag of the form "!T" +# where T is given in (fractional) megaseconds after the Unix epoch. yes, it is +# awkward, but also simple. how to convert using GNU date: +# +# $ when() { echo $(($(date +%s -d "$1") / 1e6)); } +# $ when tomorrow +# 1459.43703 +# +# (hint: round to the first decimal, a day is 86.4ks) +# +# urgent items are displayed in red. use this feature to flag items before it +# becomes too late to act on them. +# +# - items marked with a tag of the form "^T" will be ignored until time T. use +# this for items that cannot or should not be acted on before the given time. +# +# - indented (non-comment) lines below an item are interpreted as items pending +# completion of their parent. for each child, a green plus sign is appended +# to the parent item. pending items are not shown themselves but count toward +# the pain value of their parent. use this to account for but otherwise hide +# items that strictly depend on the completion of another. + + +theta=6 +nocolor=1 +maxitems=0 # infinite + +# awk program for formatted output +format=' + function color(c) { + return nocolor? "" : sprintf("\033[3%dm", c) + } + + function rgbcolor(r,g,b) { + return nocolor? "" : sprintf("\033[38;2;%d;%d;%dm", r,g,b) + } + + function grey(l) { + return rgbcolor(l,l,l) + } + + function reset() { + return nocolor? "" : "\033[m" + } + + function bold() { + return nocolor? "" : "\033[1m" + } + + { + p=0 + if(match($0, /\++$/)) { p=RLENGTH } + if(substr($0,7,1) == "^") { npost++; npost+=p; next } + nplus += p + n++ + + urgent[n] = (substr($0,7,1) == "!") + if(urgent[n]) { nurg++ } + + pain[n] = $1 + if($1 >= theta) { nhi++ } + total += $1 + + if($1 == 0) { + sub(/^...../, " ") + nun++ + } + + # shorten -tag: fields; and do it without submatch references + # i lov^Whate awk :v + gsub(/[[:space:]]-[[:alnum:]]+:/, "&<-XXX->&") + gsub(/:<-XXX->[[:space:]]-[[:alnum:]]+:[^[:space:]]*/, "...") + + item[n] = $0 + } + + END { + red = 1 + yellow = 3 + green = 2 + white = 7 + + if(maxitems && n > maxitems) { n=maxitems } + for(i=1; i<=n; i++) { + it = item[i] + if(urgent[i]) { + it = color(red) it + } else { + if(pain[i] == 0) { + base = rgbcolor(235, 167, 231) # light orchid, #EBA7E7 + } else if(pain[i] > 9) { + base = color(white) + } else { + base = grey(32+(pain[i]-1)*28) + } + it = base it + sub(/[! ] \[[^\]]*\]/, bold() color(yellow) "&" reset() base, it) + sub(/\++$/, color(green) "&", it) + } + print it + + if(i == nhi) { + print color(red), "____________________________________" \ + "_____________________________________" + } + + acc += pain[i] + if(!half && acc >= total/2) { + print color(yellow), "...................................." \ + "....................................." + half = 1 + } + } + + if(NR > 0) { + print reset() bold() # adds blank line + printf("%.1f pain \t%d active items", total, NR) + if(nun) printf(", %d unrated", nun) + if(nurg) printf(", %d urgent (%s!%s)", nurg, + color(red), reset() bold()) + if(nplus) printf(" \t%d pending (%s+%s)", nplus, + color(green), reset() bold()) + if(npost) printf(", %d postponed", npost) + print reset() + } + } +' + +if test -t 1 # is stdout a tty? +then + nocolor=0 + maxitems=$(($(tput li) - 5)) +fi + +args=`getopt npvt:l: $*` +if [ $? -ne 0 ]; then + echo "usage: $0 [-npv] [-t THRESHOLD] [-l MAX] [pattern]" + exit 1 +fi +set -- $args +while [ $# -ne 0 ]; do + case "$1" in + -n) nocolor=1; shift;; + -p) format='{print}'; shift;; + -t) theta="$2"; shift; shift;; + -v) invert=1; shift;; + -l) maxitems="$2"; shift; shift;; + --) shift; break;; + esac +done + +# collect items and attach pain values +awk ' + function product(xs) { + p = 1 + for(i in xs) { p *= xs[i] } + return p + } + + function score(s) { + if(match(s, />[1-9](x[1-9])*= urgtime * 1e6) + } + + if(match($0, / \^[0-9]+(\.[0-9]+)?/)) { + uptime = substr($0, RSTART+2, RLENGTH-2) + up = (now >= uptime * 1e6) + } + } + + END { printit() } +' pattern="$*" invert=$invert | +# sort by pain +sort -rsn -k 1 | +# format output +awk "$format" theta=$theta nocolor=$nocolor maxitems=$maxitems