saturnin.sh.skel 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. #!/bin/bash
  2. shellfu import exit
  3. shellfu import inigrep
  4. shellfu import pretty
  5. #
  6. # Saturnin - __MKIT_PROJ_TAGLINE__
  7. #
  8. # Saturnin is a Shellfu module that can help you build consistent and easy
  9. # to use command line-based toolkit. The idea is that you have a set of
  10. # scripts that together form a particular tool, which you then integrate
  11. # under a single meta-command (similar to git, apt, systemctl and many
  12. # others...).
  13. #
  14. # Saturnin's main goal is to help you with the integration so that you can
  15. # fully concentrate on development of the scripts, while Saturnin will
  16. # provide the common part of command-line interface. Adding to that,
  17. # Saturnin also provides several extra features as config management,
  18. # hooks or support for command line completion.
  19. #
  20. # It's possible to integrate any kind of programs within Saturnin, although
  21. # extra features are currently only available for Shellfu/Bash scripts.
  22. #
  23. # Starting your toolkit
  24. # =====================
  25. #
  26. # The starting point of a Saturnin-powered toolkit is the main meta-command,
  27. # which sets all mandatory variables and calls saturnin__main(). You could
  28. # implement that one yourself, but it's **strongly recommended** to start
  29. # off by creating a copy of 'app.skel' from 'saturnin-demo':
  30. #
  31. # https://github.com/AloisMahdal/saturnin-demo/
  32. #
  33. # In fact, it's best to actually copy the *whole* project, which serves
  34. # as skeleton of not just the meta-command (by Saturnin) but also MKit
  35. # install/build system, which provides features like installation
  36. # scripts, SemVer-compatible versioning and packaging templates for both
  37. # RPM and DEB:
  38. #
  39. # git clone https://github.com/AloisMahdal/saturnin-demo
  40. # mv saturnin-demo myproject
  41. # cd myproject
  42. # rm -rf .git
  43. # git init
  44. #
  45. # You will need to edit few files, for starters:
  46. #
  47. # edit mkit.ini # your main project info
  48. # edit packaging/template.spec # file lists, Requires and description
  49. # edit packaging/debian/control # ..also for Debian
  50. # git add .
  51. # git commit -m "Initial commit"
  52. #
  53. # ...but the reward is sweet:
  54. #
  55. # make install # to install to current system
  56. #
  57. # make rpmstuff # to get .spec file and source tarball
  58. # make debstuff # to get debian directory
  59. # make vbump # to seal off release
  60. # make release # to seal off release
  61. #
  62. #
  63. # Git commit hash of application source tree
  64. #
  65. # This is supposed to be set by your build scripts when building your
  66. # application. The string is returned by calling your meta-command with
  67. # option --saturnin-app-git-hash.
  68. #
  69. # Look for 'satcmd' template for a working example.
  70. #
  71. SATURNIN_APP_GIT_HASH=${SATURNIN_APP_GIT_HASH:-}
  72. #
  73. # Your application version
  74. #
  75. # This is supposed to be set by your build scripts when building your
  76. # application. The string is returned by calling your meta-command with
  77. # options --version, --version-semver or --saturnin-app-version.
  78. #
  79. # Look for 'satcmd' template for a working example.
  80. #
  81. SATURNIN_APP_VERSION=${SATURNIN_APP_VERSION:-}
  82. #
  83. # Path to user cache
  84. #
  85. SATURNIN_CACHE_HOME=${SATURNIN_CACHE_HOME:-}
  86. #
  87. # Path where saturnin__conf should look for files
  88. #
  89. # If filename does not contain slash, it is looked up in each (or all,
  90. # based on strategy--see saturnin__conf() doc) path in this list. The
  91. # list is colon-separated and non-dirs as well as empty strings are
  92. # silently ignored.
  93. #
  94. SATURNIN_CONF_PATH="${SATURNIN_CONF_PATH:-}"
  95. #
  96. # Expected config filename extension (for guessing from path head)
  97. #
  98. # If no filename to read is given, saturnin__conf() will guess filename
  99. # as the path head plus this suffix (e.g. `foo.ini` for `saturnin__conf
  100. # foo.bar.baz`)
  101. #
  102. SATURNIN_CONF_SUFFIX="${SATURNIN_CONF_SUFFIX:-.ini}"
  103. #
  104. # Directory where to look for subcommands
  105. #
  106. # Files here starting with $SATURNIN_LIBEXEC_PREFIX are considered
  107. # subcommands
  108. #
  109. SATURNIN_LIBEXEC="${SATURNIN_LIBEXEC:-}"
  110. #
  111. # Subcommand file prefix
  112. #
  113. # This is recommended to be set to meta-command name plus dash. For
  114. # example, if your meta-command is `mykit`, this should be set to
  115. # `mykit-`.
  116. #
  117. SATURNIN_LIBEXEC_PREFIX="${SATURNIN_LIBEXEC_PREFIX:-}"
  118. #
  119. # Meta-command help mode
  120. #
  121. # This controls what is displayed when user calls meta-command with --help
  122. # argument. Following formats are supported:
  123. #
  124. # +HELPFILE
  125. # =HELPFILE
  126. #
  127. # In both cases, HELPFILE must be absolute path to a file containing
  128. # human-readable description of the meta-command, which will be directly
  129. # presented to user.
  130. #
  131. # If the leading character is '=' (equal sign), the help text consists of,
  132. # and only of the HELPFILE. If the character is '+' (plus sign), the help
  133. # text is pre-pended with auto-generated usage message.
  134. #
  135. # If the value is empty, only the auto-generated usage message is printed.
  136. #
  137. SATURNIN_META_HELP="${SATURNIN_META_HELP:-}"
  138. saturnin__bug() {
  139. #
  140. # Warn about bug in your software
  141. #
  142. # Issue warning using warn() from pretty but also add application
  143. # version. This is useful when an assertion in your application fails
  144. # such that it is certain that there is a bug inside it.
  145. #
  146. # In such cases, it makes sense to print also version information to
  147. # help users with reporting.
  148. #
  149. local msg # message core
  150. for msg in "$@";
  151. do
  152. warn "bug: $msg"
  153. done
  154. warn "bug in $(basename "$0") version: $SATURNIN_APP_VERSION"
  155. }
  156. saturnin__conf() {
  157. #
  158. # inigrep smart loader
  159. #
  160. # Usage:
  161. # saturnin__conf [-j] [inigrep-query] [-- [file]..]
  162. #
  163. #
  164. # File arguments
  165. # ==============
  166. #
  167. # If omitted, *file* argument is inferred by taking part of kpath name
  168. # before first dot and appending value of `$SATURNIN_CONF_SUFFIX`,
  169. # (".ini" by default).
  170. #
  171. # Each *file* argument is then processed as follows:
  172. #
  173. # * `-` (single dash) is interpreted as reading from standard input.
  174. #
  175. # * If argument contains slash, it is expanded as a regular path
  176. # (relative or absolute).
  177. #
  178. # * Otherwise, it is taken as filename and searched for in directories
  179. # given in `$SATURNIN_CONF_PATH`. (This can yield more than one
  180. # path, which is equivalent as if all paths were provided.)
  181. #
  182. # Not all files expanded based on `$SATURNIN_CONF_PATH` are read by
  183. # default; reading is governed by "merge strategy": the default
  184. # strategy "first" reads only the first existing file.
  185. #
  186. # "join" strategy on the other hand, means that any files are simply
  187. # concatenated and prefixed with comment (visible only in raw mode)
  188. # containing path to the file.
  189. #
  190. # This means that if a section is queried that is present in both
  191. # files, it is effectively concatenated as well.
  192. #
  193. # Following calls are equivalent
  194. #
  195. # saturnin__conf foo.bar.baz
  196. # saturnin__conf foo.bar.baz foo.ini
  197. #
  198. # and both result in reading of key *baz* from section *foo.bar* in file
  199. # *foo.ini*, which is selected from *SATURNIN_CONF_PATH*. Should there
  200. # be more foo.ini's, the first is selected. Using `-j` switch
  201. #
  202. # saturnin__conf -j foo.bar.baz
  203. #
  204. # would cause all foo.ini's on *SATURNIN_CONF_PATH* be concatenated
  205. # instead.
  206. #
  207. local ig_mode # retrieval mode
  208. local ig_query # keypath or section name (when listing keys)
  209. local ig_limit # line limit
  210. local files=() # file specification
  211. local Strategy=first # merge strategy
  212. while true; do case $1:$2 in
  213. "":*) break ;;
  214. -j:*) Strategy=join; shift 1 ;;
  215. -1:*) ig_limit=$1; shift 1 ;;
  216. -e:*.*) ig_mode=$1; ig_query=$2; shift 2; break ;;
  217. -r:*.*) ig_mode=$1; ig_query=$2; shift 2; break ;;
  218. -K:*) ig_mode=$1; ig_query=$2; shift 2; break ;;
  219. -S:*) ig_mode=$1; ig_query=""; shift 1; break ;;
  220. -P:*) ig_mode=$1; ig_query=""; shift 1; break ;;
  221. .*:*) _saturnin__conf_usage -w "bad syntax: $*" ;;
  222. *.*:*) ig_mode=-e; ig_query=$1; shift 1; break ;;
  223. --help:*) _saturnin__conf_usage -e 0 ;;
  224. *) _saturnin__conf_usage -w "bad syntax: $*" ;;
  225. esac done
  226. test -n "$ig_mode" || _saturnin__conf_usage -w "could not determine inigrep mode"
  227. debug -v ig_limit ig_query ig_mode Strategy
  228. if test -n "$*";
  229. then
  230. files=("$@")
  231. elif test -n "$ig_query";
  232. then
  233. files=("${ig_query%%.*}$SATURNIN_CONF_SUFFIX")
  234. else
  235. _saturnin__conf_usage -w "dunno what to load"
  236. fi
  237. debug -v files
  238. #shellcheck disable=SC2086
  239. _saturnin__conf__load "${files[@]}" | inigrep $ig_limit $ig_mode "$ig_query"
  240. return "${PIPESTATUS[0]}"
  241. }
  242. saturnin__conf_find() {
  243. #
  244. # Find all existing instances of sub-path $1 on $SATURNIN_CONF_PATH
  245. #
  246. # Usage:
  247. #
  248. # saturnin__conf_find SUBPATH
  249. #
  250. # Go through all elements of $SATURNIN_CONF_PATH, looking for file or
  251. # directory, whose path is formed by joining SUBPATH to element of
  252. # $SATURNIN_CONF_PATH. Print each existing path, ignore rest.
  253. #
  254. # For example, with following setup:
  255. #
  256. # SATURNIN_CONF_PATH=foo:bar:baz
  257. # mkdir -p foo/one bar/one
  258. # mkdir -p bar/two/slashes
  259. #
  260. # call
  261. #
  262. # saturnin__conf_find one
  263. #
  264. # would print `foo/one` and `bar/one`, while
  265. #
  266. # saturnin__conf_find two/slashes
  267. #
  268. # would print `bar/two/slashes`.
  269. #
  270. # If at least one path was found, return zero. Otherwise, return one,
  271. # or more in case of error.
  272. #
  273. local file=$1 # sub-path to find
  274. local trydir # each item of $SATURNIN_CONF_PATH
  275. local trypath # each combined path
  276. debug -v SATURNIN_CONF_PATH
  277. echos "$SATURNIN_CONF_PATH" \
  278. | tr ':' '\n' \
  279. | while read -r trydir;
  280. do
  281. test -n "$trydir" || continue
  282. trypath="$trydir/$file"
  283. test -e "$trypath" || continue
  284. echos "$trypath"
  285. done \
  286. | grep .
  287. }
  288. saturnin__get() {
  289. #
  290. # Show Saturnin internal info by key $1 and exit
  291. #
  292. # Key $1 can be whole `--saturnin-get-stuff` argument or just the part
  293. # after `--saturnin-get-`.
  294. #
  295. # This is aimed to help debugging and testing the app (or Saturnin
  296. # itself) by showing packaging and deployment related info.
  297. #
  298. local key=${1#--saturnin-get-} # internal info key
  299. case "$key" in
  300. saturnin-conf-path) echo "$SATURNIN_CONF_PATH" ;;
  301. saturnin-version) echo "__MKIT_PROJ_VERSION__" ;;
  302. app-git-hash) echo "$SATURNIN_APP_GIT_HASH" ;;
  303. app-version) echo "$SATURNIN_APP_VERSION" ;;
  304. cache-home) echo "$SATURNIN_CACHE_HOME" ;;
  305. libexec) echo "$SATURNIN_LIBEXEC" ;;
  306. libexec-prefix) echo "$SATURNIN_LIBEXEC_PREFIX" ;;
  307. subcommands) saturnin__lssc ;;
  308. *) warn "unknown devel key: $key"
  309. exit "$EXIT_USAGE" ;;
  310. esac
  311. exit "$EXIT_OK"
  312. }
  313. saturnin__lssc() {
  314. #
  315. # List subcommands
  316. #
  317. find "$SATURNIN_LIBEXEC" \
  318. -mindepth 1 \
  319. -maxdepth 1 \
  320. -executable \
  321. -name "$SATURNIN_LIBEXEC_PREFIX*" \
  322. | sed -e "s|^.*/||; s|^$SATURNIN_LIBEXEC_PREFIX||" \
  323. | sort
  324. }
  325. saturnin__main() {
  326. #
  327. # Main meta-command entry function
  328. #
  329. # After setting all mandatory environment variables, call this from your
  330. # main meta-command script.
  331. #
  332. local subcommand # subcommand to execute (first non-option)
  333. test -n "$SATURNIN_CACHE_HOME" || die "SATURNIN_CACHE_HOME is not set"
  334. test -n "$SATURNIN_LIBEXEC" || die "SATURNIN_LIBEXEC is not set"
  335. test -n "$SATURNIN_LIBEXEC_PREFIX" || die "SATURNIN_LIBEXEC_PREFIX is not set"
  336. while true; do case $1 in
  337. -D|--full-debug) export PRETTY_DEBUG=true
  338. export PRETTY_DEBUG_EXCLUDE=""
  339. shift ;;
  340. -d|--debug) export PRETTY_DEBUG=true; shift ;;
  341. -v|--verbose) export PRETTY_VERBOSE=true; shift ;;
  342. -h|--help) saturnin__help; exit ;;
  343. --version) saturnin__version; exit ;;
  344. -V|--version-semver) saturnin__get app-version ;;
  345. --saturnin-get-*) saturnin__get "$1" ;;
  346. -*) saturnin__usage -w "unknown argument: $1" ;;
  347. --*) saturnin__usage -w "unknown argument: $1" ;;
  348. --) shift; break ;;
  349. "") saturnin__usage -w "too few arguments" ;;
  350. *) break; ;;
  351. esac done
  352. subcommand="$1"; shift
  353. PRETTY_DEBUG_EXCLUDE="" debug -v SATURNIN_APP_VERSION
  354. debug -v SATURNIN_CONF_PATH
  355. case "$subcommand" in
  356. conf) saturnin__conf "$@" ;;
  357. *) saturnin__runsc "$subcommand" "$@" ;;
  358. esac
  359. }
  360. saturnin__help() {
  361. #
  362. # Print meta-command help text
  363. #
  364. # See $SATURNIN_META_HELP for details.
  365. #
  366. local introline # introduction line
  367. introline=$(basename "$0")
  368. test -n "$SATURNIN_APP_TAGLINE" \
  369. && introline+=" - $SATURNIN_APP_TAGLINE"
  370. case "$SATURNIN_META_HELP" in
  371. "")
  372. echo "$introline"$'\n' >&2
  373. saturnin__usage -E -e 0
  374. ;;
  375. +/*)
  376. echo "$introline"$'\n' >&2
  377. saturnin__usage -E
  378. echo >&2
  379. _saturnin__cat_helpfile "${SATURNIN_META_HELP:1}"
  380. ;;
  381. =/*)
  382. _saturnin__cat_helpfile "${SATURNIN_META_HELP:1}"
  383. ;;
  384. *)
  385. echo "$introline"$'\n' >&2
  386. saturnin__usage -E
  387. saturnin__bug "malformed SATURNIN_META_HELP: $SATURNIN_META_HELP"
  388. return 3
  389. ;;
  390. esac
  391. }
  392. saturnin__conf_mkpath() {
  393. #
  394. # Compose new value for $SATURNIN_CONF_PATH from locations $@
  395. #
  396. # Usage:
  397. #
  398. # saturnin__conf_mkpath DIR [DIR..]
  399. #
  400. # Go through each DIR and print it, unless it ends with "/ini.d",
  401. # in which case list its subdirectories, sorted by C locale (this allows
  402. # for modular configuration).
  403. #
  404. # Non-existent or non-directory items are silently ignored.
  405. #
  406. local location # one location argument
  407. local path # one path listed
  408. for location in "$@";
  409. do
  410. test -d "$location" || continue
  411. case "$location" in
  412. */ini.d) # modular location--sort subfolders
  413. find -L "$location" -mindepth 1 -maxdepth 1 -type d \
  414. | LC_ALL=C sort
  415. ;;
  416. *)
  417. echo "$location"
  418. ;;
  419. esac
  420. done \
  421. | _saturnin__nl2colon
  422. }
  423. saturnin__runhook() {
  424. #
  425. # Run custom hook named $1 from respective configuration section
  426. #
  427. # Will load joined multi-line key "hook.$SATURNIN_SUBCOMMAND.$1" and
  428. # unless syntax check fails, execute it as Bash code (in separate
  429. # process).
  430. #
  431. local name="$1" # hook name
  432. local code # ... code
  433. test -n "$SATURNIN_SUBCOMMAND" || {
  434. warn "unknown subcommand, ignoring hook: $name"
  435. return 0
  436. }
  437. code="$(saturnin__conf -j "hook.$SATURNIN_SUBCOMMAND.$name")"
  438. debug -v SATURNIN_SUBCOMMAND code name
  439. bash -n <<<"$code" || {
  440. warn "syntax errors, ignoring hook: $name"
  441. return 0
  442. }
  443. bash <<<"$code"
  444. }
  445. saturnin__runsc() {
  446. #
  447. # Run subcommand $1 with arguments $2..
  448. #
  449. local subcommand="$1"; shift # subcommand to run
  450. local binpath # path to subcommand's binary
  451. binpath+="$SATURNIN_LIBEXEC/"
  452. binpath+="$SATURNIN_LIBEXEC_PREFIX$subcommand"
  453. debug -v binpath
  454. debug "\$*='$*'"
  455. test -x "$binpath" \
  456. || saturnin__usage "invalid sub-command: $subcommand"
  457. SATURNIN_SUBCOMMAND="$subcommand" "$binpath" "$@"
  458. }
  459. saturnin__usage() {
  460. #
  461. # Show usage message and exit
  462. #
  463. #shellcheck disable=SC2046
  464. mkusage "$@" \
  465. "[options] COMMAND [ARG...]" \
  466. -o \
  467. "-D, --full-debug turn on gory debugging" \
  468. "-V, --version show version and exit" \
  469. "-d, --debug turn on debugging" \
  470. "-h, --help show this help message and exit"\
  471. "-v, --verbose turn on verbosity" \
  472. -c \
  473. $(saturnin__lssc)
  474. }
  475. saturnin__version() {
  476. #
  477. # Print human-readable version info
  478. #
  479. # Basic version info is already stored in $SATURNIN_APP_VERSION,
  480. # this function prints more descriptive paragraph including Saturnin's
  481. # own version.
  482. #
  483. echo -n "$(basename "$0")"
  484. test -n "$SATURNIN_APP_TAGLINE" \
  485. && echo -n " ($SATURNIN_APP_TAGLINE)"
  486. echo -n " $SATURNIN_APP_VERSION"
  487. test -n "$SATURNIN_APP_CODENAME" \
  488. && echo -n " - $SATURNIN_APP_CODENAME"
  489. echo
  490. echo -n "Powered by Saturnin (__MKIT_PROJ_TAGLINE__)"
  491. echo -n " __MKIT_PROJ_VERSION__"
  492. echo -n " - __MKIT_PROJ_CODENAME__"
  493. echo
  494. return "$EXIT_OK"
  495. }
  496. saturnin__wraphook() {
  497. #
  498. # Wrap command $@ in hooks 'pre' and 'post'
  499. #
  500. # Run pre hook, then command $@, then post hook. Always exit with
  501. # status of the payload command, even if hooks fail. Ignore post-hook
  502. # if payload command failed.
  503. #
  504. local es=0 # exit status of this function
  505. saturnin__runhook pre
  506. "$@" || return $?
  507. es=$?
  508. saturnin__runhook post
  509. return $es
  510. }
  511. # # that what you see below this line #
  512. # INTERNAL # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  513. # # use in your code to anger the divine #
  514. _saturnin__cat_helpfile() {
  515. #
  516. # Print helpfile $1
  517. #
  518. local helpfile=$1 # path to help file
  519. cat "$helpfile" >&2 && return 0
  520. saturnin__bug "cannot print help file: $helpfile"
  521. return 3
  522. }
  523. _saturnin__conf__merge() {
  524. #
  525. # Take paths and applying merge strategy, load file(s)
  526. #
  527. local path # every path
  528. local found=false # 'true' if we got any path
  529. while read -r path;
  530. do
  531. found=true
  532. case $Strategy in
  533. first)
  534. debug "winner: $path"
  535. cat "$path"
  536. cat >/dev/null # throw away rest of paths
  537. ;;
  538. join)
  539. echo "# file: ${path/$HOME/~}"
  540. cat "$path" 2>/dev/null
  541. ;;
  542. esac
  543. done
  544. $found
  545. }
  546. _saturnin__conf__load() {
  547. #
  548. # Print contents of files specified in $@
  549. #
  550. # Each argument means possible file candidate. If candidate contains
  551. # slash, it's treated as file path and is printed directly. If it's
  552. # single dash, standard input is copied.
  553. #
  554. # In all other cases, filename is searched in all elements of variable
  555. # SATURNIN_CONF_PATH; output then depends on chosen $Strategy: with
  556. # 'first' strategy, first existing file is printed, with 'join'
  557. # strategy, all existing files are printed.
  558. #
  559. local arg # each passed argument
  560. local es=0 # exit status of this function
  561. for arg in "$@";
  562. do
  563. case $arg in
  564. -|*/*) # stdin, or path (with slash)
  565. cat "$arg" || es=3
  566. ;;
  567. *) # name given, find all its incarnations
  568. saturnin__conf_find "$arg" \
  569. | _saturnin__conf__merge; es=$?
  570. ;;
  571. esac
  572. done
  573. return $es
  574. }
  575. _saturnin__conf_usage() {
  576. #
  577. # Show usage message, passing $@ to mkusage() and exit
  578. #
  579. PRETTY_USAGE="self=${0##*/} conf" \
  580. mkusage "$@" \
  581. "[options] [-e] SECTION.KEY [FNAME]" \
  582. "[options] -r SECTION.KEY [FNAME]" \
  583. "[options] -K SECTION [FNAME]" \
  584. "[options] -P FNAME" \
  585. "[options] -S FNAME" \
  586. -- \
  587. "Use inigrep to query config files." \
  588. -o \
  589. "-j join all files before applying query" \
  590. "-1 ensure single line is returned" \
  591. -c \
  592. "-e use normal mode (default)" \
  593. "-r use raw mode (preserves RHS whitespace and some comments)"\
  594. "-K list available keys in SECTION" \
  595. "-S list available sections in FNAME" \
  596. "-P list available keypaths (SECTION.KEY) in FNAME" \
  597. -- \
  598. "FNAME is filename, which is then searched on all paths specified"\
  599. "in SATURNIN_CONF_PATH and depending on -j parameter, first one" \
  600. "wins or all are joined. If FNAME contains slash, this search is"\
  601. "not done and FNAME is taken as path to file that is then" \
  602. "queried." \
  603. "" \
  604. "If FNAME is omitted, it is inferred from SECTION (e.g. 'foo.ini'"\
  605. "if 'foo.bar' was section name; note that section name itself may"\
  606. "contain dot)."
  607. }
  608. _saturnin__nl2colon() {
  609. #
  610. # Convert newline-based list of paths to colon:based:list
  611. #
  612. # Empty paths must not be included in the resulting list, so we need to
  613. # drop them and also get the colons right.
  614. #
  615. local idx=0 # current item index (zero-based)
  616. local path # each path on stdin
  617. while read -r path;
  618. do
  619. test -z "$path" && continue
  620. test $idx -gt 0 && echo -n ':'
  621. echo -n "$path"
  622. ((idx++))
  623. done
  624. }
  625. #shellfu module-version=__MKIT_PROJ_VERSION__