saturnin.sh.skel 22KB

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