saturnin.sh.skel 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  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. debug -v SATURNIN_APP_VERSION SATURNIN_CONF_PATH
  354. case "$subcommand" in
  355. conf) saturnin__conf "$@" ;;
  356. *) saturnin__runsc "$subcommand" "$@" ;;
  357. esac
  358. }
  359. saturnin__help() {
  360. #
  361. # Print meta-command help text
  362. #
  363. # See $SATURNIN_META_HELP for details.
  364. #
  365. local introline # introduction line
  366. introline=$(basename "$0")
  367. test -n "$SATURNIN_APP_TAGLINE" \
  368. && introline+=" - $SATURNIN_APP_TAGLINE"
  369. case "$SATURNIN_META_HELP" in
  370. "")
  371. echo "$introline"$'\n' >&2
  372. saturnin__usage -E -e 0
  373. ;;
  374. +/*)
  375. echo "$introline"$'\n' >&2
  376. saturnin__usage -E
  377. echo >&2
  378. _saturnin__cat_helpfile "${SATURNIN_META_HELP:1}"
  379. ;;
  380. =/*)
  381. _saturnin__cat_helpfile "${SATURNIN_META_HELP:1}"
  382. ;;
  383. *)
  384. echo "$introline"$'\n' >&2
  385. saturnin__usage -E
  386. saturnin__bug "malformed SATURNIN_META_HELP: $SATURNIN_META_HELP"
  387. return 3
  388. ;;
  389. esac
  390. }
  391. saturnin__conf_mkpath() {
  392. #
  393. # Compose new value for $SATURNIN_CONF_PATH from locations $@
  394. #
  395. # Usage:
  396. #
  397. # saturnin__conf_mkpath DIR [DIR..]
  398. #
  399. # Go through each DIR and print it, unless it ends with "/ini.d",
  400. # in which case list its subdirectories, sorted by C locale (this allows
  401. # for modular configuration).
  402. #
  403. # Non-existent or non-directory items are silently ignored.
  404. #
  405. local location # one location argument
  406. local path # one path listed
  407. for location in "$@";
  408. do
  409. test -d "$location" || continue
  410. case "$location" in
  411. */ini.d) # modular location--sort subfolders
  412. find -L "$location" -mindepth 1 -maxdepth 1 -type d \
  413. | LC_ALL=C sort
  414. ;;
  415. *)
  416. echo "$location"
  417. ;;
  418. esac
  419. done \
  420. | _saturnin__nl2colon
  421. }
  422. saturnin__runhook() {
  423. #
  424. # Run custom hook named $1 from respective configuration section
  425. #
  426. # Will load joined multi-line key "hook.$SATURNIN_SUBCOMMAND.$1" and
  427. # unless syntax check fails, execute it as Bash code (in separate
  428. # process).
  429. #
  430. local name="$1" # hook name
  431. local code # ... code
  432. test -n "$SATURNIN_SUBCOMMAND" || {
  433. warn "unknown subcommand, ignoring hook: $name"
  434. return 0
  435. }
  436. code="$(saturnin__conf -j "hook.$SATURNIN_SUBCOMMAND.$name")"
  437. debug -v SATURNIN_SUBCOMMAND code name
  438. bash -n <<<"$code" || {
  439. warn "syntax errors, ignoring hook: $name"
  440. return 0
  441. }
  442. bash <<<"$code"
  443. }
  444. saturnin__runsc() {
  445. #
  446. # Run subcommand $1 with arguments $2..
  447. #
  448. local subcommand="$1"; shift # subcommand to run
  449. local binpath # path to subcommand's binary
  450. binpath+="$SATURNIN_LIBEXEC/"
  451. binpath+="$SATURNIN_LIBEXEC_PREFIX$subcommand"
  452. debug -v binpath
  453. debug "\$*='$*'"
  454. test -x "$binpath" \
  455. || saturnin__usage "invalid sub-command: $subcommand"
  456. SATURNIN_SUBCOMMAND="$subcommand" "$binpath" "$@"
  457. }
  458. saturnin__usage() {
  459. #
  460. # Show usage message and exit
  461. #
  462. #shellcheck disable=SC2046
  463. mkusage "$@" \
  464. "[options] COMMAND [ARG...]" \
  465. -o \
  466. "-D, --full-debug turn on gory debugging" \
  467. "-V, --version show version and exit" \
  468. "-d, --debug turn on debugging" \
  469. "-h, --help show this help message and exit"\
  470. "-v, --verbose turn on verbosity" \
  471. -c \
  472. $(saturnin__lssc)
  473. }
  474. saturnin__version() {
  475. #
  476. # Print human-readable version info
  477. #
  478. # Basic version info is already stored in $SATURNIN_APP_VERSION,
  479. # this function prints more descriptive paragraph including Saturnin's
  480. # own version.
  481. #
  482. echo -n "$(basename "$0")"
  483. test -n "$SATURNIN_APP_TAGLINE" \
  484. && echo -n " ($SATURNIN_APP_TAGLINE)"
  485. echo -n " $SATURNIN_APP_VERSION"
  486. test -n "$SATURNIN_APP_CODENAME" \
  487. && echo -n " - $SATURNIN_APP_CODENAME"
  488. echo
  489. echo -n "Powered by Saturnin (__MKIT_PROJ_TAGLINE__)"
  490. echo -n " __MKIT_PROJ_VERSION__"
  491. echo -n " - __MKIT_PROJ_CODENAME__"
  492. echo
  493. return "$EXIT_OK"
  494. }
  495. saturnin__wraphook() {
  496. #
  497. # Wrap command $@ in hooks 'pre' and 'post'
  498. #
  499. # Run pre hook, then command $@, then post hook. Always exit with
  500. # status of the payload command, even if hooks fail. Ignore post-hook
  501. # if payload command failed.
  502. #
  503. local es=0 # exit status of this function
  504. saturnin__runhook pre
  505. "$@" || return $?
  506. es=$?
  507. saturnin__runhook post
  508. return $es
  509. }
  510. # # that what you see below this line #
  511. # INTERNAL # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  512. # # use in your code to anger the divine #
  513. _saturnin__cat_helpfile() {
  514. #
  515. # Print helpfile $1
  516. #
  517. local helpfile=$1 # path to help file
  518. cat "$helpfile" >&2 && return 0
  519. saturnin__bug "cannot print help file: $helpfile"
  520. return 3
  521. }
  522. _saturnin__conf__merge() {
  523. #
  524. # Take paths and applying merge strategy, load file(s)
  525. #
  526. local path # every path
  527. local found=false # 'true' if we got any path
  528. while read -r path;
  529. do
  530. found=true
  531. case $Strategy in
  532. first)
  533. debug "winner: $path"
  534. cat "$path"
  535. cat >/dev/null # throw away rest of paths
  536. ;;
  537. join)
  538. echo "# file: ${path/$HOME/~}"
  539. cat "$path" 2>/dev/null
  540. ;;
  541. esac
  542. done
  543. $found
  544. }
  545. _saturnin__conf__load() {
  546. #
  547. # Print contents of files specified in $@
  548. #
  549. # Each argument means possible file candidate. If candidate contains
  550. # slash, it's treated as file path and is printed directly. If it's
  551. # single dash, standard input is copied.
  552. #
  553. # In all other cases, filename is searched in all elements of variable
  554. # SATURNIN_CONF_PATH; output then depends on chosen $Strategy: with
  555. # 'first' strategy, first existing file is printed, with 'join'
  556. # strategy, all existing files are printed.
  557. #
  558. local arg # each passed argument
  559. local es=0 # exit status of this function
  560. for arg in "$@";
  561. do
  562. case $arg in
  563. -|*/*) # stdin, or path (with slash)
  564. cat "$arg" || es=3
  565. ;;
  566. *) # name given, find all its incarnations
  567. saturnin__conf_find "$arg" \
  568. | _saturnin__conf__merge; es=$?
  569. ;;
  570. esac
  571. done
  572. return $es
  573. }
  574. _saturnin__conf_usage() {
  575. #
  576. # Show usage message, passing $@ to mkusage() and exit
  577. #
  578. PRETTY_USAGE="self=${0##*/} conf" \
  579. mkusage "$@" \
  580. "[options] [-e] SECTION.KEY [FNAME]" \
  581. "[options] -r SECTION.KEY [FNAME]" \
  582. "[options] -K SECTION [FNAME]" \
  583. "[options] -P FNAME" \
  584. "[options] -S FNAME" \
  585. -- \
  586. "Use inigrep to query config files." \
  587. -o \
  588. "-j join all files before applying query" \
  589. "-1 ensure single line is returned" \
  590. -c \
  591. "-e use normal mode (default)" \
  592. "-r use raw mode (preserves RHS whitespace and some comments)"\
  593. "-K list available keys in SECTION" \
  594. "-S list available sections in FNAME" \
  595. "-P list available keypaths (SECTION.KEY) in FNAME" \
  596. -- \
  597. "FNAME is filename, which is then searched on all paths specified"\
  598. "in SATURNIN_CONF_PATH and depending on -j parameter, first one" \
  599. "wins or all are joined. If FNAME contains slash, this search is"\
  600. "not done and FNAME is taken as path to file that is then" \
  601. "queried." \
  602. "" \
  603. "If FNAME is omitted, it is inferred from SECTION (e.g. 'foo.ini'"\
  604. "if 'foo.bar' was section name; note that section name itself may"\
  605. "contain dot)."
  606. }
  607. _saturnin__nl2colon() {
  608. #
  609. # Convert newline-based list of paths to colon:based:list
  610. #
  611. # Empty paths must not be included in the resulting list, so we need to
  612. # drop them and also get the colons right.
  613. #
  614. local idx=0 # current item index (zero-based)
  615. local path # each path on stdin
  616. while read -r path;
  617. do
  618. test -z "$path" && continue
  619. test $idx -gt 0 && echo -n ':'
  620. echo -n "$path"
  621. ((idx++))
  622. done
  623. }
  624. #shellfu module-version=__MKIT_PROJ_VERSION__