imapfilter convenience wrapper

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. #!/bin/bash
  2. util__gate_printable() {
  3. #
  4. # True if stdin had non-whitespace values
  5. #
  6. local value
  7. value=$(cat)
  8. grep -q '[[:print:]]' <<<"$value" || return 1
  9. echo "$value"
  10. }
  11. util__isa_bool() {
  12. #
  13. # True if $1 is a boolean value
  14. #
  15. test "$1" == false && return 0
  16. test "$1" == true && return 0
  17. return 1
  18. }
  19. util__isa_enum() {
  20. #
  21. # True if $1 is one of $2..
  22. #
  23. local value=$1; shift
  24. local alloweds=("$@")
  25. local allowed
  26. for allowed in "${alloweds[@]}"; do
  27. test "$value" == "$allowed" && return 0
  28. done
  29. return 1
  30. }
  31. util__isa_int() {
  32. #
  33. # True if $1 is an integer
  34. #
  35. # Note that $1 does not have to be decimal; in POSIX shells,
  36. # eg. 0xf1 is a valid hexadecimal integer and 0755 is a valid
  37. # octal integer.
  38. #
  39. {
  40. test -z "$1" && return 1
  41. test "$1" -ge 0 && return 0
  42. return 1
  43. } 2>/dev/null
  44. }
  45. util__isa_name() {
  46. #
  47. # True if $1 is a name in most languages
  48. #
  49. echo "$1" | grep -qx '[[:alpha:]_][[:alnum:]_]*'
  50. }
  51. util__isa_posint() {
  52. #
  53. # True if $1 is a positive integer
  54. #
  55. {
  56. test -z "$1" && return 1
  57. test "$1" -ge 0 && return 0
  58. return 1
  59. } 2>/dev/null
  60. }
  61. util__join() {
  62. #
  63. # Use $1 to join items $2..
  64. #
  65. local dlm=$1; shift
  66. local items=("$@")
  67. local item
  68. local head=true
  69. for item in "${items[@]}"; do
  70. $head || echo -n "$dlm"
  71. $head && head=false
  72. echo -n "$item"
  73. done
  74. }
  75. util__loadarr() {
  76. #
  77. # Save output lines from command $2.. to array named $1
  78. #
  79. # Usage:
  80. # util__loadarr ARRAY CMD [ARG]..
  81. #
  82. # Unlike `mapfile -t <<<"$(command)", this produces zero
  83. # items if command output was empty.
  84. #
  85. local __util__arr_fromcmd__oarr=$1; shift
  86. local __util__arr_fromcmd__cmd=("$@")
  87. local __util__arr_fromcmd__tmp
  88. local __util__arr_fromcmd__es
  89. __util__arr_val_oarr "$__util__arr_fromcmd__oarr" || return 2
  90. __util__arr_fromcmd__tmp=$(mktemp /tmp/util__loadarr.__util__arr_fromcmd__tmp.XXXXX)
  91. "${__util__arr_fromcmd__cmd[@]}" > "$__util__arr_fromcmd__tmp"; __util__arr_fromcmd__es=$?
  92. mapfile -t "$__util__arr_fromcmd__oarr" <"$__util__arr_fromcmd__tmp"
  93. rm "$__util__arr_fromcmd__tmp"
  94. return $__util__arr_fromcmd__es
  95. }
  96. util__loadenv() {
  97. #
  98. # Safely load value from envvar $1
  99. #
  100. local name=$1
  101. local value
  102. value=${!name}
  103. test -n "$value" || return 1
  104. echo "$value"
  105. }
  106. util__loadenv_bool() {
  107. #
  108. # Safely load integer from envvar $1
  109. #
  110. local name=$1
  111. __util__safe_loadenv \
  112. "$name" \
  113. util__isa_bool \
  114. "not a boolean (true|false): $name=$value"
  115. }
  116. util__loadenv_enum() {
  117. #
  118. # Safely load enum from envvar $1, allowing values $2..
  119. #
  120. local name=$1; shift
  121. local allowed_values=("$@")
  122. local value
  123. local desc_allowed
  124. value=${!name}
  125. test -n "$value" || return 1
  126. util__isa_enum "$value" "${allowed_values[@]}" || {
  127. desc_allowed=$(util__join "|" "${allowed_values[@]}")
  128. warn "not one of supported values ($desc_allowed): $name = $value"
  129. return 3
  130. }
  131. echo "$value"
  132. }
  133. util__loadenv_int() {
  134. #
  135. # Safely load integer from envvar $1
  136. #
  137. local name=$1
  138. __util__safe_loadenv \
  139. "$name" \
  140. util__isa_int \
  141. "not an integer"
  142. }
  143. util__loadenv_name() {
  144. #
  145. # Safely load positive integer from envvar $1
  146. #
  147. local name=$1
  148. __util__safe_loadenv \
  149. "$name" \
  150. util__isa_name \
  151. "not a valid name (alphanumeric, starting with letter)"
  152. }
  153. util__loadenv_posint() {
  154. #
  155. # Safely load positive integer from envvar $1
  156. #
  157. local name=$1
  158. __util__safe_loadenv \
  159. "$name" \
  160. util__isa_posint \
  161. "not a positive integer"
  162. }
  163. util__sort_by_length() {
  164. #
  165. # Sort items on stdin by length
  166. #
  167. while IFS= read -r item; do
  168. echo "${#item} $item"
  169. done \
  170. | sort -n \
  171. | cut -d' ' -f2-
  172. }
  173. util__warnbox() {
  174. #
  175. # Emit warning $2 box-decorated with char $1, follow by lines $3..
  176. #
  177. local char=$1; shift
  178. local subj=$1; shift
  179. local body=("$@")
  180. case "${#body[*]}" in
  181. 0) __util__warnbox1 "$char" "$subj" ;;
  182. *) __util__warnbox_sub "$char" "$subj" "${body[@]}" ;;
  183. esac
  184. }
  185. __util__warnbox1() {
  186. #
  187. # Emit warning $2 box-decorated with char $1
  188. #
  189. local char=$1
  190. local msg=$2
  191. local line
  192. local midline="$char$char $msg $char$char"
  193. line=$(__util__make_hr "$char" "$midline")
  194. warn "$line" \
  195. "$midline" \
  196. "$line"
  197. }
  198. __util__warnbox_sub() {
  199. #
  200. # Emit warning $2 box-decorated with char $1, follow by lines $3..
  201. #
  202. local char=$1; shift # decoration character
  203. local subj=$1; shift # local of the message subject
  204. local body=("$@") # lines of the message body
  205. local longest # longest line from subject and body
  206. local width # length of ^^
  207. local hr # horizontal ruler
  208. local vr="$char$char" # vertical ruler point
  209. local padded_body # body lines; padded
  210. local padded_subj # body line; padded
  211. local line # each of ^^^
  212. local out_lines=() # final lines
  213. longest=$(__util__longest "$subj" "${body[@]}")
  214. width=${#longest}
  215. padded_subj=$(__util__rpad "${width}" "$subj")
  216. util__loadarr padded_body __util__rpad "$width" "${body[@]}"
  217. debug_var longest width padded_subj padded_body
  218. hr=$(__util__make_hr "$char" "$vr $longest $vr")
  219. out_lines+=(
  220. "$hr"
  221. "$vr $padded_subj $vr"
  222. "$hr"
  223. )
  224. for line in "${padded_body[@]}"; do
  225. out_lines+=("$vr $line $vr")
  226. done
  227. out_lines+=("$hr")
  228. warn "${out_lines[@]}"
  229. }
  230. __util__longest() {
  231. #
  232. # Choose longest of $@
  233. #
  234. printf '%s\n' "$@" \
  235. | util__sort_by_length \
  236. | tail -n 1
  237. }
  238. __util__make_hr() {
  239. #
  240. # Repeat char $1 to the length of $2
  241. #
  242. local char=$1
  243. local stuff=$2
  244. local length=${#stuff}
  245. __util__repchar "$char" "$length"
  246. }
  247. __util__repchar() {
  248. #
  249. # Repeat char $1 $2 times
  250. #
  251. local char=$1
  252. local times=$2
  253. local togo=$times
  254. while test "$togo" -gt 0; do
  255. echo -n "$char"
  256. ((togo--))
  257. done
  258. echo
  259. }
  260. __util__rpad() {
  261. #
  262. # Right-pad all of $2.. to length $1 with space
  263. #
  264. local wid=$1; shift
  265. local rest=("$@")
  266. printf "%-${wid}s"'\n' "${rest[@]}"
  267. }
  268. __util__safe_loadenv() {
  269. #
  270. # Safely load envvar $1 with validator $2 and error message $3
  271. #
  272. local name=$1
  273. local validator=$2
  274. local msg=$3
  275. local value
  276. value=${!name}
  277. debug_var name value
  278. test -n "$value" || return 1
  279. "$validator" "$value" || {
  280. warn "$msg: $name=$value"
  281. return 3
  282. }
  283. echo "$value"
  284. }
  285. __util__isa_name() {
  286. #
  287. # True if $1 is a name in most languages
  288. #
  289. echo "$1" | grep -qx '[[:alpha:]_][[:alnum:]_]*'
  290. }
  291. __util__arr_val_oarr() {
  292. #
  293. # Validate output array named $1
  294. #
  295. local name=$1
  296. __util__isa_name "$name" || { warn "invalid array name: $name"; return 2; }
  297. }