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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. #!/bin/bash
  2. ffoo import pretty
  3. #
  4. # Expected filename extension (for guessing from -p path head)
  5. #
  6. # If no filename to read is given, cfgrep will guess filename
  7. # as the path head plus this suffix (e.g. `foo.conf` for
  8. # `cfgrep -p foo.bar.baz`)
  9. #
  10. FFOO_CONFIG_SUFFIX="${FFOO_CONFIG_SUFFIX:-.conf}"
  11. __cfgrep__cat() {
  12. # cat without starting a process
  13. while IFS= read line;
  14. do echos "$line"; done
  15. }
  16. __cfgrep__fltcmt() {
  17. grep -v -e "^[[:space:]]*[#;]" -e "^[[:space:]]*$"
  18. }
  19. __cfgrep__fltkey() {
  20. # filter values from key=value pair
  21. while IFS= read line; # ' key = val '
  22. do
  23. line="${line##*([[:space:]])}" # line='key = val '
  24. key="${line%%*([[:space:]])=*}" # key='key'
  25. value="${line##$key*([[:space:]])=}" # value=' val '
  26. $strict || value="${value##*([[:space:]])}" # value='val'
  27. test "$key" = "$wntkey" || continue
  28. $list || echos "$value"
  29. $list && echos "$line"
  30. done
  31. }
  32. __cfgrep__fltsct() {
  33. # filter per section
  34. local sct_ok=false
  35. local line
  36. while IFS= read line;
  37. do
  38. case "$line" in
  39. \[$wntsct\]) sct_ok=true; # our section
  40. $list && echos $line; # inform lister
  41. continue ;;
  42. \[*\]) sct_ok=false; continue ;; # next section
  43. esac
  44. $sct_ok || continue
  45. $strict || line="${line##*([[:space:]])}"
  46. echos "$line"
  47. done
  48. }
  49. __cfgrep__fltlst() {
  50. local key="__NOKEY__" line="" sct="__NOSCT__"
  51. while read line;
  52. do
  53. case "$line" in
  54. \[*\]) sct=${line#[}; sct=${sct%]}; key=__NOKEY__ ;;
  55. *=*) key="${line%%*([[:space:]])=*}" ;; # key='key'
  56. *) continue ;; # whatever
  57. esac
  58. case "$listobj:$sct:$key" in
  59. p*:__NOSCT__:*) echos "$sct.$key" ;;
  60. p*:*:__NOKEY__) continue ;;
  61. p*:*:*) echos "$sct.$key" ;;
  62. s*:__NOSCT__:*) echos "$sct" ;;
  63. s*:*:__NOKEY__) echos "$sct" ;;
  64. s*:*:*) continue ;;
  65. k*:__NOSCT__:__NOKEY__) continue ;;
  66. k*:__NOSCT__:*) echos "$key" ;;
  67. k*:*:__NOKEY__) continue ;;
  68. k*:*:*) echos "$key" ;;
  69. esac
  70. done
  71. }
  72. __cfgrep__merge() {
  73. local path
  74. debug -v strategy
  75. while read path;
  76. do
  77. debug -v path
  78. case $strategy in
  79. first)
  80. local winner
  81. if test -f "$path";
  82. then
  83. debug "winner: $path"
  84. cat "$path"
  85. cat >/dev/null # throw away rest of paths
  86. fi
  87. ;;
  88. join)
  89. debug -v path
  90. echo "# file: ${path/$HOME/~}"
  91. cat "$path" 2>/dev/null
  92. ;;
  93. esac
  94. done
  95. }
  96. __cfgrep__load() {
  97. if test -z "$1";
  98. then # guess filename from section head
  99. local guess="${wntsct%%.*}$FFOO_CONFIG_SUFFIX"
  100. debug -v guess
  101. __cfgrep__load "$guess"
  102. fi
  103. local arg trydir trypath
  104. for arg in "$@";
  105. do
  106. case $arg in
  107. -|*/*) # stdin, or path (with slash)
  108. debug -v arg
  109. cat "$arg"
  110. ;;
  111. *) # name given, find all its incarnations
  112. debug -v FFOO_CONFIG_PATH
  113. echos "$FFOO_CONFIG_PATH" \
  114. | tr ':' '\n' \
  115. | while read trydir;
  116. do
  117. test -n "$trydir" || continue
  118. trypath="$trydir/$arg"
  119. debug -v trypath
  120. echos "$trypath"
  121. done \
  122. | __cfgrep__merge
  123. ;;
  124. esac
  125. done
  126. true
  127. }
  128. cfgrep() {
  129. #
  130. # A flexible (not just) .conf/.ini file reader
  131. #
  132. # Usage:
  133. # cfgrep [-S] [-1] [-j] [-s section] [-k key] [file...]
  134. # cfgrep [-S] [-1] [-j] [-p path] [file...]
  135. #
  136. #
  137. # Selection
  138. # =========
  139. #
  140. # Probably most apparent use case is selection of key from
  141. # a section of INI file. This can be achieved using
  142. # *path*, which is equivalent to providing both *key* and
  143. # *section* (i.e. `-p section.key` is the same as
  144. # `-s section -k key`).
  145. #
  146. # However, you can also provide only *section* to select
  147. # only that section (leaving any key=value pairs intact),
  148. # or provide only *key* to select only that key from *all*
  149. # sections in file (if there are any). This in fact extends
  150. # use of this function beyond just INI-like format: if you
  151. # are brave enough, you could e.g. query a shell script for
  152. # a variable assignment (i.e. key=value pair).
  153. #
  154. # If suitable key is found at multiple lines, all values
  155. # are printed, unless you provided *-1* option. This allows
  156. # for creating multi-line values.
  157. #
  158. # Leading/trailing spaces and empty lines are removed from
  159. # output by default, but strict mode (*-S* or *--strict*
  160. # switch) changes this si that values after `=` are left
  161. # intact. Using strict mode when not selecting key or
  162. # section results in preserving empty lines and comments
  163. # as well.
  164. #
  165. #
  166. # File arguments
  167. # ==============
  168. #
  169. # Each *file* argument is processed as follows:
  170. #
  171. # * `-` (single dash), it is interprerted as reding from
  172. # STDIN.
  173. #
  174. # * If argument contains slash, it is expanded as a regular
  175. # path.
  176. #
  177. # * Otherwise, it is taken as filename and searched for
  178. # in directories given in `$FFOO_CONFIG_PATH`. (This can
  179. # yield more than one path, which is equivalent as if
  180. # all paths were provided.)
  181. #
  182. # Not all files expanded based on `$FFOO_CONFIG_PATH`
  183. # are read by default; reading is governed by "merge
  184. # strategy": the default strategy "first" reads only
  185. # the first existing file. "join" strategy means
  186. # that any files are simply concatenated and prefixed
  187. # with comment containing path to the file, which can
  188. # be observed in strict mode. (This means that if a
  189. # section is queried that is present in both files,
  190. # it is effectively concatenated as well.
  191. #
  192. # The merge strategy can be specified using parameter
  193. # `--merge-strategy`, while `-j` or `--join` is a
  194. # shorthand for `--merge-strategy join`
  195. #
  196. # Without *file* given at all, same procedure is used as in
  197. # case of filename without slash, except that *file* argument
  198. # is inferred by taking part of section name before first
  199. # dot and adding `$FFOO_CONFIG_SUFFIX`, (".conf" by default).
  200. # This allows for creating relatively rich config value
  201. # structure it divided in several files (e.g. by component)
  202. # but accessing them with as little typing as possible.
  203. #
  204. local wntsct=""
  205. local wntkey=""
  206. local list=false
  207. local listobj="paths"
  208. local strict=false
  209. local sct_ok=true
  210. local one_line=false
  211. local grepex="." # i.e. throw away empty lines
  212. local strategy="first"
  213. while true; do case $1 in
  214. --) break ;;
  215. -1|--one-line) one_line=true; shift 1 ;;
  216. -j|--join) strategy="join"; shift 1 ;;
  217. -k|--key) wntkey="$2"; shift 2 ;;
  218. -p|--path) wntkey="${2##*.}"; wntsct="${2%.$wntkey}"; shift 2 ;;
  219. -s|--section) wntsct="$2"; sct_ok=false; shift 2 ;;
  220. -S|--strict) strict=true; shift 1 ;;
  221. -l) list=true; listobj=paths; shift 1 ;;
  222. -L|--list) list=true; listobj=$2; shift 2 ;;
  223. --merge-strategy) strategy="$2"; shift 2 ;;
  224. *) break ;;
  225. esac done
  226. case "$strategy" in
  227. first|join) : ;;
  228. *) warn "invalid merge strategy: $strategy"; return 2 ;;
  229. esac
  230. case $listobj in
  231. p|paths) : ;;
  232. k|keys) : ;;
  233. s|sections) : ;;
  234. *) warn "invalid object type to list: $listobj" ;;
  235. esac
  236. fltsct=__cfgrep__cat
  237. fltkey=__cfgrep__cat
  238. fltcmt=__cfgrep__fltcmt
  239. fltlst=__cfgrep__cat
  240. limit_line=__cfgrep__cat
  241. test -n "$wntsct" && fltsct=__cfgrep__fltsct
  242. test -n "$wntkey" && fltkey=__cfgrep__fltkey
  243. $list && fltlst=__cfgrep__fltlst
  244. $one_line && limit_line="tail -1"
  245. $strict && grepex=""
  246. $strict && fltcmt=__cfgrep__cat
  247. debug -v wntkey wntsct list listobj
  248. debug "\$@='$@'"
  249. local restore_extglob=$(shopt -p extglob)
  250. shopt -s extglob
  251. __cfgrep__load "$@" \
  252. | $fltcmt \
  253. | $fltsct \
  254. | $fltkey \
  255. | $fltlst \
  256. | $limit_line \
  257. | grep "$grepex"
  258. $restore_extglob
  259. }