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

config.sh 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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, inigrep will guess filename
  7. # as the path head plus this suffix (e.g. `foo.ini` for
  8. # `inigrep -p foo.bar.baz`)
  9. #
  10. FFOO_CONFIG_SUFFIX="${FFOO_CONFIG_SUFFIX:-.ini}"
  11. __inigrep__cat() {
  12. #
  13. # cat without starting a process
  14. #
  15. while IFS= read line;
  16. do echos "$line"; done
  17. }
  18. __inigrep__fltcmt() {
  19. #
  20. # Just strip comments
  21. #
  22. grep -v -e "^[[:space:]]*[#;]" -e "^[[:space:]]*$"
  23. }
  24. __inigrep__fltkey() {
  25. #
  26. # Filter values from `key = value pairs`
  27. #
  28. # In listing mode, prints *key names* instead
  29. #
  30. while IFS= read line; # ' key = val '
  31. do
  32. line="${line##*([[:space:]])}" # line='key = val '
  33. key="${line%%*([[:space:]])=*}" # key='key'
  34. value="${line##$key*([[:space:]])=}" # value=' val '
  35. $strict || value="${value##*([[:space:]])}" # value='val'
  36. test "$key" = "$wntkey" || continue
  37. $list || echos "$value"
  38. $list && echos "$line"
  39. done
  40. }
  41. __inigrep__fltsct() {
  42. #
  43. # Filter out only the wanted section body
  44. #
  45. # In listing mode prints also section name
  46. #
  47. local sct_ok=false
  48. local line
  49. while IFS= read line;
  50. do
  51. case "$line" in
  52. \[$wntsct\]) sct_ok=true; # our section
  53. $list && echos $line; # inform lister
  54. continue ;;
  55. \[*\]) sct_ok=false; continue ;; # next section
  56. esac
  57. $sct_ok || continue
  58. $strict || line="${line##*([[:space:]])}"
  59. echos "$line"
  60. done
  61. }
  62. __inigrep__fltlst() {
  63. #
  64. # Filter only section/keys/path names from the stream
  65. #
  66. local key="__NOKEY__" line="" sct="__NOSCT__"
  67. while read line;
  68. do
  69. case "$line" in
  70. \[*\]) sct=${line#[}; sct=${sct%]}; key=__NOKEY__ ;;
  71. *=*) key="${line%%*([[:space:]])=*}" ;; # key='key'
  72. *) continue ;; # whatever
  73. esac
  74. case "$listobj:$sct:$key" in
  75. p*:__NOSCT__:*) echos "$sct.$key" ;;
  76. p*:*:__NOKEY__) continue ;;
  77. p*:*:*) echos "$sct.$key" ;;
  78. s*:__NOSCT__:*) echos "$sct" ;;
  79. s*:*:__NOKEY__) echos "$sct" ;;
  80. s*:*:*) continue ;;
  81. k*:__NOSCT__:__NOKEY__) continue ;;
  82. k*:__NOSCT__:*) echos "$key" ;;
  83. k*:*:__NOKEY__) continue ;;
  84. k*:*:*) echos "$key" ;;
  85. esac
  86. done
  87. }
  88. __inigrep__merge() {
  89. #
  90. # Take paths and applying merge strategy, load file(s)
  91. #
  92. local path
  93. debug -v strategy
  94. while read path;
  95. do
  96. test -f "$path" || continue
  97. debug -v path
  98. case $strategy in
  99. first)
  100. debug "winner: $path"
  101. cat "$path"
  102. cat >/dev/null # throw away rest of paths
  103. ;;
  104. join)
  105. echo "# file: ${path/$HOME/~}"
  106. cat "$path" 2>/dev/null
  107. ;;
  108. esac
  109. done
  110. }
  111. __inigrep__load() {
  112. #
  113. # Find file(s) candidates and load them (or use *__merge)
  114. #
  115. test -n "$1" || __inigrep__load "${wntsct%%.*}$FFOO_CONFIG_SUFFIX"
  116. local arg trydir trypath
  117. for arg in "$@";
  118. do
  119. case $arg in
  120. -|*/*) # stdin, or path (with slash)
  121. debug -v arg
  122. cat "$arg"
  123. ;;
  124. *) # name given, find all its incarnations
  125. debug -v FFOO_CONFIG_PATH
  126. echos "$FFOO_CONFIG_PATH" \
  127. | tr ':' '\n' \
  128. | while read trydir;
  129. do
  130. test -n "$trydir" || continue
  131. trypath="$trydir/$arg"
  132. debug -v trypath
  133. echos "$trypath"
  134. done \
  135. | __inigrep__merge
  136. ;;
  137. esac
  138. done
  139. true
  140. }
  141. inigrep() {
  142. #
  143. # A flexible (not just) .ini file reader
  144. #
  145. # Usage:
  146. # inigrep [-S] [-1] [-j] -p section.key [file...]
  147. # inigrep [-S] [-1] [-j] [-s section] [-k key] [file...]
  148. #
  149. #
  150. # Selection
  151. # =========
  152. #
  153. # Probably most apparent use case is selection of key from
  154. # a section of INI file--the first usage form. Note that
  155. # *section* itself may contain dots; only last part after all
  156. # dots is parsed as *key*.
  157. #
  158. # The second form lets you provide only *section* to select
  159. # only that section (leaving any key=value pairs intact),
  160. # or provide only *key* to select only that key from *all*
  161. # sections in file (if there are any). This in fact extends
  162. # use of this function beyond just INI-like format: if you
  163. # are brave enough, you could e.g. query a shell script for
  164. # a variable assignment (i.e. key=value pair).
  165. #
  166. # If suitable key is found at multiple lines, all values
  167. # are printed, which allows for creating multi-line values.
  168. # Providing *-1* argument, however, always prints only one
  169. # line.
  170. #
  171. # Leading/trailing spaces and empty lines are removed from
  172. # output by default, but strict mode (*-S* or *--strict*
  173. # switch) changes this so that values after `=` are left
  174. # intact. Using strict mode when not selecting key or
  175. # section results in preserving empty lines and comments
  176. # as well.
  177. #
  178. #
  179. # File arguments
  180. # ==============
  181. #
  182. # If omitted, *file* argument is inferred by taking part of
  183. # section name before first dot and appending value of
  184. # `$FFOO_CONFIG_SUFFIX`, (".ini" by default).
  185. #
  186. # Each *file* argument is then processed as follows:
  187. #
  188. # * `-` (single dash) is interpreted as reading from
  189. # STDIN.
  190. #
  191. # * If argument contains slash, it is expanded as a regular
  192. # path (relative or absolute).
  193. #
  194. # * Otherwise, it is taken as filename and searched for
  195. # in directories given in `$FFOO_CONFIG_PATH`. (This can
  196. # yield more than one path, which is equivalent as if
  197. # all paths were provided.)
  198. #
  199. # Not all files expanded based on `$FFOO_CONFIG_PATH`
  200. # are read by default; reading is governed by "merge
  201. # strategy": the default strategy "first" reads only
  202. # the first existing file.
  203. #
  204. # "join" strategym on the other hand, means that any
  205. # files are simply concatenated and prefixed with
  206. # comment containing path to the file. (This means that
  207. # if a section is queried that is present in both files,
  208. # it is effectively concatenated as well.)
  209. #
  210. # Note that the comment can be seen only in strict mode
  211. # and will be included even for non-existent files.
  212. #
  213. # The merge strategy can be specified using parameter
  214. # `--merge-strategy`, while `-j` or `--join` is a
  215. # shorthand for `--merge-strategy join`
  216. #
  217. #
  218. # Examples
  219. # ========
  220. #
  221. # Following calls are equivalent
  222. #
  223. # inigrep -p foo.bar.baz
  224. # inigrep -p foo.bar.baz foo.ini
  225. #
  226. # and result in reading of key *baz* from section *foo.bar*
  227. # in file *foo.ini*, which is selected from *FFOO_CONFIG_PATH*.
  228. # Should there be more foo.ini's, the first is selected.
  229. # Using `-j` switch
  230. #
  231. # inigrep -j -p foo.bar.baz
  232. #
  233. # would cause any of such files be concatenated.
  234. #
  235. # inigrep -s qux ./corge.ini
  236. #
  237. # will open corge.ini in current directory, and print every
  238. # range of lines delimited by line containing only `[qux]`
  239. # and another section delimiter or end of file. Comments and
  240. # empty lines will be stripped.
  241. #
  242. # cat graply | inigrep -S -k grault -
  243. #
  244. # will split every line in graply that starts with word
  245. # "grault" surrounded by any amount of whitespace and
  246. # followed by equals sign, printing part after the equals
  247. # sign. The -S` (strict mode) will preserve *right hand
  248. # side*, ie. will not strip any spaces after "=".
  249. #
  250. local wntsct="" # desired section name
  251. local wntkey="" # desired key name
  252. local list=false # listing mode?
  253. local listobj="paths" # what to list (paths/sections/keys)
  254. local strict=false # strict mode?
  255. local sct_ok=true # are we reading a desired section
  256. local one_line=false # limit to single line
  257. local grepex="." # i.e. throw away empty lines
  258. local strategy="first" # merge strategy
  259. while true; do case $1 in
  260. --) break ;;
  261. -1|--one-line) one_line=true; shift 1 ;;
  262. -j|--join) strategy="join"; shift 1 ;;
  263. -k|--key) wntkey="$2"; shift 2 ;;
  264. -p|--path) wntkey="${2##*.}"; wntsct="${2%.$wntkey}"; shift 2 ;;
  265. -s|--section) wntsct="$2"; sct_ok=false; shift 2 ;;
  266. -S|--strict) strict=true; shift 1 ;;
  267. -l) list=true; listobj=paths; shift 1 ;;
  268. -L|--list) list=true; listobj=$2; shift 2 ;;
  269. --merge-strategy) strategy="$2"; shift 2 ;;
  270. *) break ;;
  271. esac done
  272. case "$strategy" in
  273. first|join) : ;;
  274. *) warn "invalid merge strategy: $strategy"; return 2 ;;
  275. esac
  276. case $listobj in
  277. p|paths) : ;;
  278. k|keys) : ;;
  279. s|sections) : ;;
  280. *) warn "invalid object type to list: $listobj"; return 2 ;;
  281. esac
  282. fltsct=__inigrep__cat # section filter
  283. fltkey=__inigrep__cat # key filter
  284. fltcmt=__inigrep__fltcmt # comment filter
  285. fltlst=__inigrep__cat # listing mode filter
  286. limit_line=__inigrep__cat # limiter filter
  287. test -n "$wntsct" && fltsct=__inigrep__fltsct
  288. test -n "$wntkey" && fltkey=__inigrep__fltkey
  289. $list && fltlst=__inigrep__fltlst
  290. $one_line && limit_line="tail -1"
  291. $strict && grepex=""
  292. $strict && fltcmt=__inigrep__cat
  293. debug -v wntkey wntsct list listobj
  294. debug "\$@='$@'"
  295. local restore_extglob=$(shopt -p extglob)
  296. shopt -s extglob
  297. __inigrep__load "$@" \
  298. | $fltcmt \
  299. | $fltsct \
  300. | $fltkey \
  301. | $fltlst \
  302. | $limit_line \
  303. | grep "$grepex"
  304. $restore_extglob
  305. }