inigrep.sh.skel 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. #!/bin/bash
  2. shellfu import pretty
  3. #
  4. # grep for (some) INIs
  5. #
  6. # inigrep is designed to read a particular simplistic dialect of
  7. # INI configuration files. In a sense, it can be considered as
  8. # a "grep for INIs", in that rather than parsing the file into
  9. # a typed memory structure for later access, it passes file each
  10. # time a query is done, and spits out relevant parts; treating
  11. # everything as text. Hence, it's not intended as replacement
  12. # for a full-blown configuration system but rather a quick & dirty
  13. # "swiss axe" for quick & dirty scripts.
  14. #
  15. # That's not to say that you cannot do things nicely; but don't
  16. # count on speed -- well since you're using bash you obviously
  17. # don't -- and compliance -- simple things are simple, but there
  18. # are a bit unusual pitfalls.
  19. #
  20. #
  21. # The format by examples
  22. # ----------------------
  23. #
  24. # The most basic example understood by inigrep is identical
  25. # to most INI formats:
  26. #
  27. # # Let's call this simple.ini
  28. #
  29. # [foo]
  30. # bar = baz
  31. # qux = quux
  32. #
  33. # [corge]
  34. # grault = graply
  35. #
  36. # Structure here is obvious: two sections named `foo` and `corge`,
  37. # the first one has two key/value pairs and the other has one pair.
  38. #
  39. # Getting values from this file is trivial:
  40. #
  41. # inigrep foo.bar simple.ini
  42. # inigrep foo.qux simple.ini
  43. # inigrep corge.grault simple.ini
  44. #
  45. # would list `baz`, `quux` and `graply`.
  46. #
  47. # This is where 80% of use cases are covered.
  48. #
  49. #
  50. # Multi-line
  51. # ----------
  52. #
  53. # Multi-line values are rather unusual but very simple:
  54. #
  55. # [lipsum]
  56. #
  57. # latin = Lorem ipsum dolor sit amet, consectetur adipiscing
  58. # latin = elit, sed do eiusmod tempor incididunt ut labore et
  59. # latin = dolore magna aliqua. Ut enim ad minim veniam, quis
  60. # latin = ...
  61. #
  62. # english = [32] But I must explain to you how all this mistaken
  63. # english = idea of denouncing of a pleasure and praising pain
  64. # english = was born and I will give you a complete account of
  65. # english = ...
  66. #
  67. # This file can be read as:
  68. #
  69. # inigrep lipsum.latin lipsum.ini
  70. # inigrep lipsum.english lipsum.ini
  71. #
  72. #
  73. # Exploration
  74. # -----------
  75. #
  76. # Other than basic value retrieval, inigrep allows you to look around
  77. # first. For example, to list all keypaths available in a file:
  78. #
  79. # inigrep -P simple.ini
  80. #
  81. # In case of simple.ini, this would print:
  82. #
  83. # foo.bar
  84. # foo.qux
  85. # corge.grault
  86. #
  87. # Similarly:
  88. #
  89. # inigrep -S simple.ini
  90. #
  91. # would list just the section names:
  92. #
  93. # foo
  94. # corge
  95. #
  96. # and
  97. #
  98. # inigrep -K foo simple.ini
  99. #
  100. # would list all keys from section 'foo'
  101. #
  102. # bar
  103. # qux
  104. #
  105. _inigrep__fltcmt() {
  106. #
  107. # Just strip comments
  108. #
  109. perl -ne '
  110. chomp;
  111. next if m/^[[:space:]]*[#;]/;
  112. s/ *#.*//;
  113. s/ *;.*//;
  114. print "$_\n" if $_;
  115. '
  116. }
  117. _inigrep__fltkey() {
  118. #
  119. # Filter values from `key = value pairs`
  120. #
  121. # In listing mode, prints *key names* instead
  122. #
  123. local line # every line
  124. local key # key name part
  125. local value # name part
  126. while IFS= read -r line; # ' key = val '
  127. do
  128. line="${line##*([[:space:]])}" # line='key = val '
  129. key="${line%%*([[:space:]])=*}" # key='key'
  130. value="${line##$key*([[:space:]])=}" # value=' val '
  131. $StrictMode || value="${value##*([[:space:]])}" # value='val'
  132. test "$key" = "$WantedKey" || continue
  133. $ListingMode || echos "$value"
  134. $ListingMode && echos "$line"
  135. done
  136. }
  137. _inigrep__fltsct() {
  138. #
  139. # Filter out only the wanted section body
  140. #
  141. # In listing mode prints also section name
  142. #
  143. local sct_ok=false # is this the section we want?
  144. local sct_name # wanted section name
  145. local line # current line
  146. while IFS= read -r line;
  147. do
  148. if [[ $line =~ ^[[:space:]]*\[[^]]*\].* ]]; # IOW: 'spaces[name]anything'
  149. then
  150. # hack out only the name between '[' and ']'
  151. sct_name=$line
  152. sct_name=${sct_name##*([[:space:]])[} # drop leading space
  153. sct_name=${sct_name%%]*} # and trailing anything
  154. if test "$sct_name" == "$WantedSection";
  155. then
  156. sct_ok=true; # our section
  157. $ListingMode && echos "[$sct_name]"; # inform lister
  158. else
  159. sct_ok=false
  160. fi
  161. continue
  162. else
  163. $sct_ok || continue
  164. $StrictMode || line="${line##*([[:space:]])}"
  165. echos "$line"
  166. fi
  167. done
  168. }
  169. _inigrep__fltlst() {
  170. #
  171. # Filter only section/keys/path names from the stream
  172. #
  173. local line="" # current line
  174. local key="__NOKEY__" # current key
  175. local sct="__NOSCT__" # current section
  176. while read -r line;
  177. do
  178. case "$line" in
  179. \[*\]) sct=${line#[}; sct=${sct%]}; key=__NOKEY__ ;;
  180. *=*) key="${line%%*([[:space:]])=*}" ;; # key='key'
  181. *) continue ;; # whatever
  182. esac
  183. case "$Engine:$sct:$key" in
  184. lspth:__NOSCT__:*) echos "$sct.$key" ;;
  185. lspth:*:__NOKEY__) continue ;;
  186. lspth:*:*) echos "$sct.$key" ;;
  187. lssct:__NOSCT__:*) echos "$sct" ;;
  188. lssct:*:__NOKEY__) echos "$sct" ;;
  189. lssct:*:*) continue ;;
  190. lskey:__NOSCT__:__NOKEY__) continue ;;
  191. lskey:__NOSCT__:*) echos "$key" ;;
  192. lskey:*:__NOKEY__) continue ;;
  193. lskey:*:*) echos "$key" ;;
  194. esac
  195. done
  196. }
  197. _inigrep__query() {
  198. #
  199. # Query INI stream composed of files at $@ or stdin
  200. #
  201. # Compose filter pipe of potential parts and run it.
  202. #
  203. local pipe="" # filter pipe
  204. local ListingMode=false # listing mode on?
  205. local StrictMode=false # strict mode on?
  206. local ifile # current input file
  207. local restore_shopt # shopt backup (executable code)
  208. case $Engine in
  209. raw) StrictMode=true ;;
  210. ls*) ListingMode=true ;;
  211. esac
  212. # which will come first? prefix all and strip the first prefix
  213. #
  214. $StrictMode || pipe+=" | _inigrep__fltcmt"
  215. test -n "$WantedSection" && pipe+=" | _inigrep__fltsct"
  216. test -n "$WantedKey" && pipe+=" | _inigrep__fltkey"
  217. $ListingMode && pipe+=" | _inigrep__fltlst"
  218. $StrictMode || pipe+=" | grep ."
  219. pipe="${pipe# | }"
  220. test -z "$pipe" && pipe="cat" # possible with raw mode and dot wildcard
  221. debug -v pipe
  222. # pray and run
  223. #
  224. restore_shopt=$(shopt -p extglob)
  225. shopt -s extglob
  226. for ifile in "$@";
  227. do
  228. case "$ifile" in
  229. -) eval "$pipe" ;;
  230. *) <"$ifile" eval "$pipe" ;;
  231. esac
  232. done
  233. $restore_shopt
  234. }
  235. _inigrep__validate_ks() {
  236. #
  237. # Check $WantedSection ans $WantedKey for invalid chars and warn
  238. #
  239. case $WantedKey in
  240. *\\*) warn "invalid char '\\' in key name: $WantedKey"; return 1 ;;
  241. *[*) warn "invalid char '[' in key name: $WantedKey"; return 1 ;;
  242. *=*) warn "invalid char '=' in key name: $WantedKey"; return 1 ;;
  243. esac
  244. case $WantedSection in
  245. *]*) warn "invalid char ']' in section name: $WantedSection"; return 1 ;;
  246. esac
  247. }
  248. inigrep() {
  249. #
  250. # Query INI file(s) for data
  251. #
  252. # Usage:
  253. #
  254. # inigrep [-1] [section].[key] [file]...
  255. # inigrep [-1] -e|--basic [section].[key] [file]...
  256. # inigrep [-1] -r|--raw [section].[key] [file]...
  257. # inigrep [-1] -K|--lskey section [file]...
  258. # inigrep [-1] -S|--lssct [file]...
  259. # inigrep [-1] -P|--lspth [file]...
  260. #
  261. # First form implies the basic engine.
  262. #
  263. # Option *-r* switches to raw mode, in which comments,
  264. # empty lines and whitespace are all preserved in applicable
  265. # contexts.
  266. #
  267. # Key *keypath* consists of section name and key name delimited
  268. # by dot. Note that keypath may contain dots but key may not.
  269. #
  270. # If *file* is not given or is a single dash, standard input
  271. # is read. Note that standard input is not duplicated in case
  272. # of multiple dashes.
  273. #
  274. # If suitable key is found at multiple lines, all values
  275. # are printed, which allows for creating multi-line values.
  276. # Providing *-1* argument, however, always prints only one
  277. # line.
  278. #
  279. # Options -K, -S and -P can be used for inspecting file structure;
  280. # -K needs an argument of section name and will list all keys from
  281. # that section. -S will list all sections and -P will list all
  282. # existing keypaths.
  283. #
  284. #
  285. # #### Examples ####
  286. #
  287. # Having INI file such as
  288. #
  289. # [foo]
  290. # bar=baz
  291. # quux=qux
  292. # quux=qux2
  293. # quux=qux3
  294. #
  295. # * `inigrep foo.bar ./file.ini` gives "bar".
  296. # * `inigrep foo.quux ./file.ini` gives three lines "qux", "qux2"
  297. # and "qux3".
  298. # * `inigrep -P ./file.ini` gives two lines "foo.bar" and "foo.quux".
  299. # * `inigrep -K foo ./file.ini` gives two lines "bar" and "quux".
  300. # * `inigrep -S ./file.ini` gives "foo".
  301. #
  302. local kpath="" # desired key path
  303. local one_line=false # limit to single line
  304. local Engine=ini # engine: ini,raw,lspth,lssct,lskey
  305. local WantedSection="" # desired section name (in case of listing)
  306. local WantedKey="" # desired key name
  307. while true; do case $1 in
  308. -1|--one-line) one_line=true; shift ;;
  309. -e|--basic) Engine=ini; kpath="$2"; shift; break ;;
  310. -r|--raw) Engine=raw; kpath="$2"; shift; break ;;
  311. -K|--lskey) Engine=lskey; kpath="$2."; shift; break ;;
  312. -P|--lspth) Engine=lspth; kpath="."; break ;;
  313. -S|--lssct) Engine=lssct; kpath="."; break ;;
  314. *) break ;;
  315. esac done
  316. test -n "$kpath" || kpath="$1" && shift
  317. test -z "$*" && set -- -
  318. debug -v one_line Engine kpath
  319. debug "\$*='$*'"
  320. WantedKey="${kpath##*.}"
  321. WantedSection="${kpath%.$WantedKey}"
  322. _inigrep__validate_ks || return 2
  323. debug -v WantedKey WantedSection
  324. if $one_line;
  325. then
  326. _inigrep__query "$@" | head -1
  327. else
  328. _inigrep__query "$@"
  329. fi
  330. }
  331. #shellfu module-version=__MKIT_PROJ_VERSION__