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

ini.sh 7.6KB

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