shell dot on steroids https://pagure.io/shellfu

pretty.sh 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. #!/bin/bash
  2. shellfu import exit
  3. #
  4. # Application debug mode
  5. #
  6. PRETTY_DEBUG=${PRETTY_DEBUG:-false}
  7. #
  8. # List of module/function names to exclude from debuging
  9. #
  10. # For the sake of readability of your debug dumps, you can set this
  11. # variable to comma-separated list of module or function names that
  12. # you don't expect to get useful info from.
  13. #
  14. # If the caller has a qualified name (`modname__funcname()`, according
  15. # to Shellfu naming scheme it's possible to use just module name here
  16. # to mute all debug from that module (including internal functions).
  17. # Otherwise, full function name should be listed.
  18. #
  19. PRETTY_DEBUG_EXCLUDE=${PRETTY_DEBUG_EXCLUDE:-}
  20. #
  21. # Application verbosity mode
  22. #
  23. PRETTY_VERBOSE=${PRETTY_VERBOSE:-false}
  24. #
  25. # Name of pretty-printer module
  26. #
  27. # Friendly name of module used for decorating output. For
  28. # example, if the value is NAME, a _pretty_NAME module must
  29. # exist and be importable. Otherwise pretty will fall back
  30. # to plain, which is also the default value.
  31. #
  32. PRETTY=${PRETTY:-plain}
  33. #
  34. # Usage display mode
  35. #
  36. # How to determine program name for purposes of usage messages.
  37. #
  38. # If empty, use basename of "$0". Set to 'subcommand' to remove first
  39. # dash (so that `path/to/foo-bar becomes `foo bar` which may make more
  40. # sense in meta-command scenrarios). Set to self=NAME to force NAME.
  41. #
  42. PRETTY_USAGE=${PRETTY_USAGE:-}
  43. __shellfu_pretty__init() {
  44. #
  45. # Import proper submodule
  46. #
  47. if shellfu try_import "_pretty_${PRETTY}";
  48. then
  49. shellfu import "_pretty_${PRETTY}"
  50. return 0
  51. else
  52. warn "falling back to _pretty_plain"
  53. PRETTY=plain
  54. shellfu import "_pretty_${PRETTY}"
  55. fi
  56. }
  57. ##--------------------##
  58. ## PRINTING FRONT-END ##
  59. ##--------------------##
  60. debug() {
  61. #
  62. # You already know what it does
  63. #
  64. # BTW, following are equivalent:
  65. #
  66. # debug "var1=$var1" "var2=$var2" "result=$result"
  67. # debug -v var1 var2 result
  68. #
  69. $PRETTY_DEBUG || return 0
  70. _pretty__echo "$@"
  71. }
  72. debug_pipe() {
  73. #
  74. # Debug the whole pipe.
  75. #
  76. while IFS= read -r line;
  77. do
  78. debug "|$1: '$line'"
  79. echos "$line"
  80. done
  81. }
  82. die() {
  83. #
  84. # A fatal error
  85. #
  86. _pretty__echo -t
  87. _pretty__echo "$@"
  88. exit_error
  89. }
  90. echos() {
  91. #
  92. # Safer version of echo able to echo "-n"
  93. #
  94. # Traditional echo is broken in that it does not
  95. # distinguish between string to print and its own switches
  96. # (-e, -E or -n), leading to unexpected behavior.
  97. #
  98. # This echo version circumvents this by using printf.
  99. #
  100. printf -- '%s\n' "$@"
  101. }
  102. mkusage() {
  103. #
  104. # Echo out usage patterns and (by default) `exit 2`
  105. #
  106. # mkusage [-e STATUS] [-E] pattern [patern...]
  107. #
  108. # Each pattern is prefixed by "usage: " and a resolved
  109. # script/function name to produce traditional usage hint.
  110. # By default, will exit with status *EXIT_USAGE*, you
  111. # can use `-e STATUS` to specify other number. `-k` can be
  112. # used as shorthand for `-e 0` (e.g. if user asked for
  113. # the patterns)
  114. #
  115. # Use `-E` to prevent exiting; in that case, the status
  116. # that would be fed to `exit` will be used as exit status
  117. # of this function.
  118. #
  119. # Use "--" to delimit end of arguments processed by mkusage.
  120. #
  121. # Recommended usage is to define usage() in your script and
  122. # use this in its body. That way you only need to define
  123. # usage patterns once and skip to them from any place where
  124. # you detect incorrect usage.
  125. #
  126. local es=$EXIT_USAGE # our exit status
  127. local doexit=true # should we exit?
  128. while true; do case "$1" in
  129. -e) es="$2"; shift 2 || return 2 ;;
  130. -E) doexit=false; shift ;;
  131. -k) es=$EXIT_OK; shift ;;
  132. --) shift; break ;;
  133. *) break ;;
  134. esac done
  135. _pretty__echo -u "$@";
  136. $doexit && exit "$es"
  137. return "$es"
  138. }
  139. mkhelp() {
  140. #
  141. # Echo out help text
  142. #
  143. # mkhelp [-e STATUS] [-E] arg...
  144. #
  145. # By default, will exit with status *EXIT_OK*, you
  146. # can use `-e STATUS` to specify other number.
  147. #
  148. # Use `-E` to prevent exiting; in that case, the status
  149. # that would be fed to `exit` will be used as exit status
  150. # of this function.
  151. #
  152. # Use "--" to delimit end of arguments processed by mkhelp
  153. #
  154. local es=$EXIT_OK
  155. local doexit=true
  156. while true; do case "$1" in
  157. -e) es="$2"; shift 2 || return 2 ;;
  158. -E) doexit=false; shift ;;
  159. --) shift; break ;;
  160. *) break ;;
  161. esac done
  162. _pretty__echo "$@"
  163. $doexit && exit "$es"
  164. return "$es"
  165. }
  166. think() {
  167. #
  168. # If verbose is on, think loud
  169. #
  170. # Use "-l" to split every parameter to separate line, (useful
  171. # or longer warnings)
  172. #
  173. $PRETTY_VERBOSE || return 0
  174. _pretty__echo "$@"
  175. }
  176. warn() {
  177. #
  178. # Warn them
  179. #
  180. # Use "-l" to split every parameter to separate line, (useful
  181. # or longer warnings)
  182. #
  183. _pretty__echo "$@"
  184. }
  185. ##----------##
  186. ## BACK-END ##
  187. ##----------##
  188. _pretty__cat() {
  189. #
  190. # `cat` but without starting a process
  191. #
  192. # Used to avoid spanning new process where stream handler is chosen
  193. # based on some logic
  194. #
  195. while IFS= read -r line;
  196. do echos "$line"; done
  197. }
  198. _pretty__get_caller() {
  199. #
  200. # Get first user function and negative index from stack
  201. #
  202. local fname
  203. local nidx="${#FUNCNAME[@]}"
  204. for fname in "${FUNCNAME[@]}";
  205. do
  206. (( nidx-- ))
  207. _pretty__is_internal && continue
  208. _pretty__is_frontend && continue
  209. test "$fname" = "usage" && continue
  210. echos "$nidx" "$fname"
  211. return
  212. done
  213. }
  214. _pretty__get_frontend() {
  215. #
  216. # Get entry point function name from stack
  217. #
  218. local fname
  219. for fname in "${FUNCNAME[@]}";
  220. do
  221. _pretty__is_internal && continue
  222. _pretty__is_frontend && echos "$fname" && return 0
  223. echo "do not call _pretty_* directly: $fname" >&2
  224. return "$EXIT_USAGE"
  225. done
  226. }
  227. _pretty__is_excluded() {
  228. #
  229. # True if $caller is excluded based on PRETTY_DEBUG_EXCLUDE
  230. #
  231. # Check PRETTY_DEBUG_EXCLUDE to see if $caller (using only module name
  232. # part, if possible) should be muted from debugging.
  233. #
  234. local listed # item listed in PRETTY_DEBUG_EXCLUDE
  235. local name # module part of caller's name
  236. local qualified # is caller "qualified" (ac. to shellfu scheme)?
  237. name="$caller"
  238. case "$name" in
  239. __*__*) qualified=true ;;
  240. __*) qualified=false ;;
  241. *__*) qualified=true ;;
  242. *) qualified=false ;;
  243. esac
  244. if $qualified;
  245. then
  246. # we'll use only the module part of the name
  247. name=${name#_} # drop one "internal" prefix
  248. name=${name#_} # drop yet another one
  249. name=${name%%__*} # drop funcname
  250. fi
  251. for listed in ${PRETTY_DEBUG_EXCLUDE//,/ };
  252. do
  253. test "$name" = "$listed" && return 0
  254. done
  255. return 1
  256. }
  257. _pretty__is_frontend() {
  258. #
  259. # True if $fname is one of our "frontends"
  260. #
  261. case "$fname" in
  262. debug) return 0 ;;
  263. debug_pipe) return 0 ;;
  264. die) return 0 ;;
  265. mkhelp) return 0 ;;
  266. think) return 0 ;;
  267. mkusage) return 0 ;;
  268. warn) return 0 ;;
  269. esac
  270. return 1
  271. }
  272. _pretty__is_internal() {
  273. #
  274. # True if $fname is our internal function
  275. #
  276. case "$fname" in
  277. _pretty__*) return 0 ;;
  278. *) return 1 ;;
  279. esac
  280. }
  281. _pretty__echo() {
  282. #
  283. # A smarter echo backend
  284. #
  285. # A smarter backend for debug, warn, think, die and
  286. # mkusage.
  287. #
  288. # -c cmd echo output of a command
  289. # -f file echo output of a file (- for stdin)
  290. # -l line [line...] echo each line separately
  291. # -t add stack trace to output
  292. # -u patt [patt...] convert each patt to usage pattern
  293. # -v var [var...] show contents of each var
  294. #
  295. local frontend # who (of pretty.sh) was called (=> prettyprinter choice)
  296. local caller # which user's function (or script) called it
  297. # ^ ^ eg. if user calls 'debug hello' from function 'foo', then
  298. # : :.. * frontend is 'debug'
  299. # :......... * and caller is 'foo'.
  300. local caller_nidx # negative stack index of caller
  301. local caller_is_main # true if caller was main script or main() in it
  302. local provider # which provider (_pretty__echo_*()) to use
  303. frontend="$(_pretty__get_frontend)" || exit_usage
  304. read caller_nidx caller <<<$(_pretty__get_caller)
  305. test "$frontend" = debug && _pretty__is_excluded "$caller" && return 0
  306. case $caller_nidx:$caller in
  307. 0:*) caller_is_main=true; caller="${0##*/}" ;;
  308. 1:main) caller_is_main=true; caller="${0##*/}" ;;
  309. *:usage) frontend=mkusage ;;
  310. *) caller_is_main=false ;;
  311. esac
  312. while true; do case $1 in
  313. -c|--cmd) provider=cmd; shift; break ;;
  314. -f|--files) provider=files; shift; break ;;
  315. -l|--lines) provider=lines; shift; break ;;
  316. -t|--trace) provider=trace; shift; break ;;
  317. -u|--usage) provider=usage; shift; break ;;
  318. -v|--vars) provider=vars; shift; break ;;
  319. *) provider=args; break ;;
  320. esac done
  321. _pretty__echo_$provider "$@" \
  322. | _pretty__$frontend >&2
  323. }
  324. _pretty__echo_args() {
  325. #
  326. # The simplest (but safe) printing of args
  327. #
  328. echos "$*"
  329. }
  330. _pretty__echo_cmd() {
  331. #
  332. # Print command line, launch it and report exit status
  333. #
  334. local c=\$
  335. test "$(id -u)" -eq 0 && c="#"
  336. echo "$c $*"
  337. "$@"
  338. echo "^ exit status: $?"
  339. }
  340. _pretty__echo_files() {
  341. #
  342. # Print names and contents of existing files
  343. #
  344. local fp
  345. for fp in "$@";
  346. do
  347. if test "$fp" = "-";
  348. then
  349. cat
  350. elif test -s "$fp" || test "$fp" = "/dev/stdin";
  351. then
  352. echo "-- $fp --"
  353. cat "$fp"
  354. fi
  355. done
  356. }
  357. _pretty__echo_lines() {
  358. #
  359. # Echo each argument as a separate line
  360. #
  361. local l;
  362. for l in "$@"; do _pretty__echo_args "$l"; done
  363. }
  364. _pretty__echo_trace() {
  365. #
  366. # Print "decorated" call trace (only in debug mode)
  367. #
  368. $PRETTY_DEBUG || return 0
  369. local depth
  370. echo "== trace =="
  371. for depth in $(seq 0 ${#FUNCNAME});
  372. do
  373. caller "$depth" || break
  374. done \
  375. | tail -n +3 \
  376. | sed -e '
  377. s/^\([^ ]\+\) \([^ ]\+\) \(.*\)/\3:\1:\2()/
  378. # ^line^, ^func^, ^file^
  379. 1 s/^/ -> /g
  380. 2,$ s/^/ /
  381. ' \
  382. | tac
  383. }
  384. _pretty__echo_help() {
  385. local oldverbose="$PRETTY_VERBOSE"
  386. think -l "$@"
  387. PRETTY_VERBOSE=$oldverbose
  388. }
  389. _pretty__echo_usage() {
  390. #
  391. # Compose conventional usage guide
  392. #
  393. # The default mode treats each argument as usage pattern
  394. # (see below for details). Additional formatting can be
  395. # conveniently achieved by switching to other modes, which
  396. # automatically brings necessary headers and indentations
  397. # where needed.
  398. #
  399. # * option mode (`-o`) prints "options:" header and
  400. # indents next arguments,
  401. #
  402. # * command mode (`-c`) prints "commands:" header and
  403. # indents next arguments,
  404. #
  405. # * plain mode (`--`) prints empty line (new paragraph)
  406. # and turns indentations off.
  407. #
  408. # * usage mode (`-u`, active by default), prints
  409. # "usage:" header, indents next arguments and prefixes
  410. # them with name of the script. See also $PRETTY_USAGE.
  411. #
  412. # A special case of usage mode is when only single
  413. # argument is passed to this function; then instead
  414. # of "usage:" header, this usage pattern is prefixed
  415. # with the same string.
  416. #
  417. # In order to help avoid (rare) conflict between mkusage()
  418. # switches and your usage patterns, the very first argument,
  419. # and each argument that comes right after one of these
  420. # switches are guarranteed not to be interpreted as switch.
  421. #
  422. local self # the script name
  423. local mode=usage # mode
  424. local esc=1 # escape (take next argument as literal)
  425. local arg # argument to iterate
  426. case "$PRETTY_USAGE" in
  427. self=*) self=${PRETTY_USAGE#self=} ;;
  428. subcommand) self="${0##*/}"; self="${self/-/ }" ;;
  429. *) self="$caller" ;;
  430. esac
  431. case $# in
  432. 0) return 0 ;;
  433. 1) echo "usage: $self $1"; return 0 ;;
  434. esac
  435. echo usage:
  436. for arg in "$@";
  437. do
  438. case $esc:$arg in
  439. 0:--) shift; mode=plain; esc=1; echo ;;
  440. 0:-c) shift; mode=indent; esc=1; echo; echo commands: ;;
  441. 0:-i) shift; mode=indent; esc=1; echo ;;
  442. 0:-o) shift; mode=indent; esc=1; echo; echo options: ;;
  443. 0:-u) shift; mode=usage; esc=1 ;;
  444. *) esc=0
  445. case $mode in
  446. usage) echo " $self $arg" ;;
  447. indent) echo " $arg" ;;
  448. plain) echos "$arg" ;;
  449. esac
  450. ;;
  451. esac
  452. done
  453. }
  454. _pretty__echo_vars() {
  455. #
  456. # Report value of each named variable
  457. #
  458. local varname
  459. local declare_str
  460. for varname in "$@";
  461. do
  462. if ! _pretty__is_word "$varname";
  463. then
  464. warn "unsafe value skipped: $varname";
  465. continue
  466. fi
  467. if declare_str=$(declare -p "$varname" 2>/dev/null);
  468. then
  469. _pretty__echo "${declare_str#declare ?? }"
  470. else
  471. _pretty__echo "$varname #Unset"
  472. fi
  473. done
  474. }
  475. _pretty__is_word() {
  476. #
  477. # Check if $1 contains only alphanumeric chars or _
  478. #
  479. local tainted="$1"
  480. local clean=$(tr -c -d '_[:alnum:]' <<< "$tainted")
  481. test "$tainted" = "$clean"
  482. }