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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. #!/bin/bash
  2. ##-----------##
  3. ## FRONT-END ##
  4. ##-----------##
  5. debug() {
  6. #
  7. # You already know what it does.
  8. #
  9. # BTW, following are equivalent:
  10. #
  11. # debug "var1=$var1" "var2=$var2" "result=$result"
  12. # debug -v var1 var2 result
  13. # debug -v "var@ result"
  14. #
  15. $FFOO_DEBUG || return 0
  16. __echo "$@"
  17. }
  18. debug_pipe() {
  19. #
  20. # Debug the whole pipe.
  21. #
  22. grep . \
  23. | while IFS= read line;
  24. do
  25. debug "|$1: '$line'"
  26. echo "$line"
  27. done
  28. }
  29. die() {
  30. #
  31. # A Perl-style death
  32. #
  33. __echo -t
  34. __echo $*
  35. exit 9
  36. }
  37. filter() {
  38. debug "\$*=$*"
  39. while read item;
  40. do $* $item && echo "$item"; done
  41. }
  42. usage_is() {
  43. #
  44. # Echo out usage patterns and exit 1
  45. #
  46. # Define usage() in your script and use this in body
  47. #
  48. __echo -u "$@";
  49. exit 1
  50. }
  51. think() {
  52. #
  53. # If verose is on, think loud.
  54. #
  55. # Use "-l" to split every parameter to separate line, (useful
  56. # or longer warnings)
  57. #
  58. $FFOO_VERBOSE || return 0
  59. __echo "$@"
  60. }
  61. warn() {
  62. #
  63. # Warn them
  64. #
  65. # Use "-l" to split every parameter to separate line, (useful
  66. # or longer warnings)
  67. #
  68. # Resist temptation to prefix "warning" or similar BS here.
  69. # STDERR is *made* for warnings.
  70. #
  71. __echo "$@"
  72. }
  73. ##----------##
  74. ## BACK-END ##
  75. ##----------##
  76. __cat() {
  77. # cat without starting a process
  78. while IFS= read line;
  79. do echo "$line"; done
  80. }
  81. __echo() {
  82. #
  83. # A smarter echo backend
  84. #
  85. # A smarter backend for debug, warn, think and die.
  86. #
  87. # -f file echo output of a file (- for stdin)
  88. # -c cmd echo output of a command
  89. # -l line [line...] take each line and
  90. # echo it separately
  91. # -v vars show contents of each var
  92. # -d call `decorate` with name of function
  93. # that called __echo and each argument
  94. # given
  95. #
  96. ffoo import mkpretty_${FFOO_MKPRETTY}
  97. local caller=${FUNCNAME[2]}
  98. local front=${FUNCNAME[1]}
  99. case "$caller" in
  100. usage) caller="${FUNCNAME[3]}"; ;&
  101. __echo_*) caller="${FUNCNAME[3]}"; ;&
  102. debug_pipe) caller="${FUNCNAME[3]}"; ;&
  103. main) caller="$(basename $0)"; ;;
  104. *) caller="$caller()" ;;
  105. esac
  106. test "$front" == "usage" && front=usage_is
  107. local mkpretty=__cat
  108. local src="args"
  109. while true; do case $1 in
  110. -c|--cmd) src=cmd; shift; break ;;
  111. -f|--files) src=files; shift; break ;;
  112. -l|--lines) src=lines; shift; break ;;
  113. -t|--trace) src=trace; shift; break ;;
  114. -u|--usage) src=usage; shift; break ;;
  115. -v|--vars) src=vars; shift; break ;;
  116. *) break ;;
  117. esac done
  118. $want_pretty && mkpretty=mkpretty_$front
  119. case $front in
  120. warn|debug|die|usage_is) __echo_$src "$@" | $mkpretty >&2 ;;
  121. think) __echo_$src "$@" | $mkpretty ;;
  122. *) echo "do not call __echo* directly" >&2; exit 2 ;;
  123. esac
  124. }
  125. __echo_args() {
  126. echo "$@"
  127. }
  128. __echo_cmd() {
  129. local c=\$
  130. test $(id -u) -eq 0 && c=\#
  131. echo "$c $*"
  132. $@
  133. }
  134. __echo_files() {
  135. local fp
  136. for fp in "$@";
  137. do
  138. echo "-- $fp --"
  139. cat $1
  140. done
  141. }
  142. __echo_lines() {
  143. local l;
  144. for l in "$@"; do __echo "$l"; done
  145. }
  146. __echo_trace() {
  147. $FFOO_DEBUG || return 0
  148. local -a tr=(${FUNCNAME[@]})
  149. unset tr[0]; unset tr[1]; unset tr[2];
  150. local tmp=$(echo "${tr[*]}" | tr ' ' '\n' | tac )
  151. echo "== trace =="
  152. echo "$tmp" | sed -e 's/^/-> /g'
  153. }
  154. __echo_usage() {
  155. local u
  156. for u in "$@";
  157. do echo "usage: $caller $u"
  158. done
  159. }
  160. __echo_vars() {
  161. local vn
  162. for vn in "$@";
  163. do
  164. local heel="${vn:0-1}" # last char
  165. local toes="${vn%%$heel}" # all but last char
  166. case "$heel" in
  167. @)
  168. # FIXME: review+fix the eval (even at cost of
  169. # dropping the feature)
  170. local vars=$(eval "echo \"\${!$toes$heel}\"")
  171. __echo_vars $vars
  172. ;;
  173. *)
  174. debug "$vn='$(echo -n ${!vn})'"
  175. ;;
  176. esac
  177. done
  178. }
  179. echo_hr() {
  180. #
  181. # A horizontal ruler out of char "$1" all across terminal.
  182. #
  183. test 0$COLUMNS -gt 0 || return
  184. local char="$1"
  185. local i
  186. for (( i=1; $i<$COLUMNS; i=$i+1 )); do echo -n "$char"; done
  187. echo
  188. }
  189. mute_known() {
  190. #
  191. # Mute known messages
  192. #
  193. # For those yums and rpms that don't know theit manners.
  194. # Use with care!
  195. #
  196. grep -vxf <(iniread -s mute -k $1 mute.ini)
  197. }
  198. append_if_missing() {
  199. #
  200. # Append line to the file but only if it's not already there.
  201. #
  202. # Handy for your fstabs, or whatever line-based confs Handy
  203. # for your fstabs, or whatever line-based confs
  204. #
  205. local line=$1
  206. local file=$2
  207. grep -qsx "$line" $file || echo "$line" >> $file
  208. }
  209. collapse_tilde() {
  210. #
  211. # Exchange home back to "~".
  212. #
  213. # Nice to save them users from some eye pain.
  214. #
  215. perl -pe "s|^$HOME|~|"
  216. }
  217. expand_tilde() {
  218. #
  219. # Exchange "~" for home.
  220. #
  221. perl -pe "s|^[[:space:]]*~|$HOME|"
  222. }
  223. __iniread__cat() {
  224. # cat without starting a process
  225. while IFS= read line;
  226. do echo "$line"; done
  227. }
  228. __iniread__flt_comments() {
  229. grep -v -e "^[[:space:]]*[#;]" -e "^[[:space:]]*$"
  230. }
  231. __iniread__flt_key() {
  232. # filter values from key=value pair
  233. local line
  234. local key
  235. local value
  236. while IFS= read line;
  237. do
  238. line=$(sed -re 's/^\s*//' <<<"$line")
  239. key=$(sed -re 's/\s*=.*//; s/^\s*//' <<<"$line")
  240. if $strict;
  241. then
  242. value=$(sed -re 's/[^=]*=//' <<<"$line")
  243. else
  244. value=$(sed -re 's/[^=]*=\s*//' <<<"$line")
  245. fi
  246. if test "$key" = "$wantkey";
  247. then
  248. echo "$value"
  249. fi
  250. done
  251. }
  252. __iniread__flt_section() {
  253. # filter per section
  254. local section_ok=false
  255. local line
  256. while IFS= read line;
  257. do
  258. if grep -qse "^\[$wantsection\]" <<<"$line";
  259. then
  260. section_ok=true
  261. elif grep -qse "^\[" <<<"$line";
  262. then
  263. section_ok=false
  264. elif $section_ok;
  265. then
  266. $strict || line="$(sed -re 's/^\s*//' <<<"$line")"
  267. echo "$line"
  268. fi
  269. done
  270. }
  271. __iniread__merge() {
  272. if test -z "$1";
  273. then # read only stdin
  274. cat
  275. return
  276. fi
  277. local arg trydir trypath
  278. for arg in "$@";
  279. do
  280. debug -v arg
  281. case $arg in
  282. -|*/*) # stdin, or path (with slash)
  283. cat $arg
  284. ;;
  285. *) # name given, find all its incarnations
  286. debug -v FFOO_INIPATH
  287. echo "$FFOO_INIPATH" \
  288. | tr ':' '\n' \
  289. | while read trydir;
  290. do
  291. trypath="$trydir/$arg"
  292. debug -v trypath
  293. cat $trypath 2>/dev/null
  294. done
  295. ;;
  296. esac
  297. done
  298. true
  299. }
  300. iniread() {
  301. #
  302. # A flexible (not just) .ini file reader
  303. #
  304. # Can read anything from commented plaintext to keyless section INI,
  305. # sectionless keyed INI or sectioned keyed INI, including multi-line
  306. # and whitespace-safe chunks.
  307. #
  308. # Is constantly becoming smarter and smarter.
  309. #
  310. local wantsection=""
  311. local wantkey=""
  312. local strict=false
  313. local section_ok=true
  314. local one_line=false
  315. while true;
  316. do
  317. case $1 in
  318. --)
  319. break
  320. ;;
  321. -1|--one-line)
  322. one_line=true
  323. shift 1
  324. ;;
  325. -k|--key)
  326. wantkey="$2"
  327. shift 2
  328. ;;
  329. -p|--path)
  330. wantkey="$(echo $2 | rev | cut -d. -f1 | rev)"
  331. wantsection="${2%.$wantkey}"
  332. shift 2
  333. ;;
  334. -s|--section)
  335. wantsection="$2"
  336. section_ok=false
  337. shift 2
  338. ;;
  339. -S|--strict)
  340. strict=true
  341. shift 1
  342. ;;
  343. -l|--list-sections)
  344. #TODO: list sections
  345. warn "--list-sections is not implemented"
  346. shift
  347. ;;
  348. -L|--list-keys)
  349. #TODO: list keys as section/key (with -s, list keys only)
  350. warn "--list-keys is not implemented"
  351. shift
  352. ;;
  353. "")
  354. break
  355. ;;
  356. *)
  357. break
  358. ;;
  359. esac
  360. done
  361. flt_section=__iniread__cat
  362. flt_key=__iniread__cat
  363. limit_line=__iniread__cat
  364. test -n "$wantsection" && flt_section=__iniread__flt_section
  365. test -n "$wantkey" && flt_key=__iniread__flt_key
  366. $one_line && limit_line="head -1"
  367. debug -v wantkey wantsection
  368. debug "\$@='$@'"
  369. __iniread__merge "$@" \
  370. | __iniread__flt_comments \
  371. | $flt_section \
  372. | $flt_key \
  373. | $limit_line
  374. }