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

shellfu.sh.skel 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. #!/bin/sh
  2. #shellcheck disable=SC2039,SC1090
  3. #
  4. # Shell compatibility
  5. #
  6. # This should be the binary name of the shell we're running and is used to
  7. # choose where to look for modules
  8. #
  9. SHELLFU_COMPAT=${SHELLFU_COMPAT:-$(ps -p$$ -ocmd= | cut -d' ' -f1 | rev | cut -d/ -f1 | rev)}
  10. #
  11. # Our installation directory
  12. #
  13. SHELLFU_DIR=${SHELLFU_DIR:-__SHELLFU_DIR__}
  14. #
  15. # Shellfu internals debug mode
  16. #
  17. SHELLFU_DEBUGINIT=${SHELLFU_DEBUGINIT:-false}
  18. #
  19. # Target to list imported functions in embedding mode
  20. #
  21. # In embedding mode, we need to log list of imported
  22. # modules in an external file.
  23. #
  24. SHELLFU_EMBEDTMP=${SHELLFU_EMBEDTMP:-}
  25. #
  26. # True if running in embedded mode (imports do nothing)
  27. #
  28. SHELLFU_EMBEDDED=${SHELLFU_EMBEDDED:-false}
  29. #
  30. # Last git commit hash before build
  31. #
  32. SHELLFU_GIT_HASH=__MKIT_PROJ_GIT_LASTHASH__
  33. #
  34. # Module folder prefix
  35. #
  36. # See shellfu() documentation for details about module loading.
  37. #
  38. SHELLFU_INCLUDE=${SHELLFU_INCLUDE:-$SHELLFU_DIR/include}
  39. #
  40. # Colon separated list of additional paths to search modules
  41. #
  42. # See shellfu() documentation for details about module loading.
  43. #
  44. SHELLFU_PATH=${SHELLFU_PATH:-}
  45. #
  46. # Shellfu version (SemVer 2.0)
  47. #
  48. SHELLFU_VERSION=__MKIT_PROJ_VERSION__
  49. #
  50. # Colon-separated list of already imported modules
  51. #
  52. __SHELLFU_IMPORTED=${__SHELLFU_IMPORTED:-}
  53. shellfu() {
  54. #
  55. # Shellfu init function
  56. #
  57. # Usage:
  58. #
  59. # shellfu ACTION MODULE
  60. #
  61. # MODULE is selected and acted upon, depending on ACTION:
  62. #
  63. # * `import` will source the module using shell built-in dot command.
  64. #
  65. # * `try_import` will try to import the module in a separate environment
  66. # and exit with zero on success (it will not modity the current
  67. # environment).
  68. #
  69. # * `version` will look for directive `#shellfu module-version=VER`
  70. # and print the VER part. VER must not contain spaces. Exit status
  71. # is zero if a non-empty VER has been found.
  72. #
  73. # The module lookup algorithm triest to find file MODULE.sh in following
  74. # directories:
  75. #
  76. # 1. Any directories in colon-separated list SHELLFU_PATH,
  77. #
  78. # 2. directory formed by joining SHELLFU_INCLUDE and SHELLFU_COMPAT
  79. # with dingle dash,
  80. #
  81. # 3. directory formed by joining 'sh' and SHELLFU_COMPAT with single
  82. # dash.
  83. #
  84. # for example, following code:
  85. #
  86. # #!/bin/bash
  87. #
  88. # . $(sfpath)
  89. #
  90. # SHELLFU_INCLUDE=/shellfu/include
  91. # SHELLFU_PATH=/shellfu/addon1:/shellfu/addon2
  92. #
  93. # shellfu import foo
  94. #
  95. # will import first existing file of these:
  96. #
  97. # /shellfu/addon1/foo.sh
  98. # /shellfu/addon2/foo.sh
  99. # /shellfu/include-bash/foo.sh
  100. # /shellfu/include-sh/foo.sh
  101. #
  102. # Note that SHELLFU_COMPAT should be auto-detected by shellfu and should
  103. # contain name of current shell binary being executed. This means that
  104. # modules can exist in multiple versions for each supported shell, and
  105. # when loading, shellfu prefers the shell-specific implementation.
  106. #
  107. local cmd=$1; shift
  108. local msg
  109. # debug on import
  110. case $SHELLFU_DEBUGINIT:$1 in
  111. true:import)
  112. shellfu __debug "\$*='$*'"
  113. shellfu __debug "SHELLFU_COMPAT='$SHELLFU_COMPAT'"
  114. shellfu __debug "SHELLFU_DIR='$SHELLFU_DIR'"
  115. shellfu __debug "SHELLFU_DEBUGINIT='$SHELLFU_DEBUGINIT'"
  116. shellfu __debug "SHELLFU_EMBEDDED='$SHELLFU_EMBEDDED'"
  117. shellfu __debug "SHELLFU_EMBEDTMP='$SHELLFU_EMBEDTMP'"
  118. shellfu __debug "SHELLFU_GIT_HASH='$SHELLFU_GIT_HASH'"
  119. shellfu __debug "__SHELLFU_IMPORTED='$__SHELLFU_IMPORTED'"
  120. shellfu __debug "SHELLFU_INCLUDE='$SHELLFU_INCLUDE'"
  121. shellfu __debug "SHELLFU_PATH='$SHELLFU_PATH'"
  122. shellfu __debug "SHELLFU_VERSION='$SHELLFU_VERSION'"
  123. ;;
  124. esac
  125. case $cmd in
  126. __debug)
  127. $SHELLFU_DEBUGINIT || return 0
  128. for msg in "$@"; do
  129. echo "shellfu:debug: $msg" >&2
  130. done
  131. ;;
  132. __die)
  133. for msg in "$@"; do
  134. echo "shellfu:fatal: $msg" >&2
  135. done
  136. test -z "$PS1" && exit 3
  137. return 3
  138. ;;
  139. __warn)
  140. for msg in "$@"; do
  141. echo "shellfu: $msg" >&2
  142. done
  143. ;;
  144. __usage)
  145. echo "shellfu:usage: shellfu $1" >&2
  146. test -z "$PS1" && exit 3
  147. return 3
  148. ;;
  149. import)
  150. #
  151. # Import module named $1
  152. #
  153. local mname=$1
  154. shellfu _is_imported "$mname" && return 0
  155. if shellfu _do_import "$mname"; then
  156. __SHELLFU_IMPORTED="$__SHELLFU_IMPORTED${__SHELLFU_IMPORTED:+:}$mname"
  157. else
  158. shellfu __die "cannot import module: $mname"
  159. fi
  160. ;;
  161. reload)
  162. #
  163. # Reload module named $1 (unsafe!)
  164. #
  165. local mname=$1
  166. shellfu _is_imported "$mname" || {
  167. shellfu __warn "cannot reload; not imported yet: $mname"
  168. return 2
  169. }
  170. if ! shellfu _do_import "$mname"; then
  171. shellfu __die "cannot reload module: $mname"
  172. fi
  173. ;;
  174. try_import)
  175. #
  176. # Try if module $1 could be imported
  177. #
  178. local mname=$1
  179. ( shellfu _do_import "$mname" )
  180. ;;
  181. version)
  182. #
  183. # Show version of module named $1
  184. #
  185. local mname=$1
  186. local mpath
  187. mpath=$(shellfu _select_mfile "$mname") || {
  188. shellfu __warn "cannot find module: $mname"
  189. return 3
  190. }
  191. shellfu __debug "found module file: $mpath"
  192. shellfu _read_directive "module-version" "$mpath"
  193. ;;
  194. ls_imported)
  195. #
  196. # Print list of modules already imported
  197. #
  198. echo "$__SHELLFU_IMPORTED" | tr : \\n
  199. ;;
  200. _do_import)
  201. #
  202. # Really import module named $1
  203. #
  204. $SHELLFU_EMBEDDED && return 0
  205. local mname=$1
  206. local es=4
  207. local mpath
  208. shellfu __debug "importing module $mname"
  209. mpath=$(shellfu _select_mfile "$mname") || {
  210. shellfu __warn "cannot find module: $mname"
  211. return 3
  212. }
  213. shellfu __debug "found module file: $mpath"
  214. bash -n "$mpath" || return 3
  215. . "$mpath"
  216. es=$?
  217. test -n "$SHELLFU_EMBEDTMP" && echo "$mpath" >> "$SHELLFU_EMBEDTMP"
  218. local init=__shellfu_${mname}__init
  219. if test "$(command -v "$init")" = "$init"; then
  220. shellfu __debug "launching init function: $init"
  221. $init
  222. es=$?
  223. fi
  224. return $es
  225. ;;
  226. _is_imported)
  227. #
  228. # True if module $1 is already imported
  229. #
  230. local mname=$1
  231. echo "$__SHELLFU_IMPORTED" | tr : \\n | grep -qxe "$mname"
  232. ;;
  233. _list_mfiles)
  234. #
  235. # Find module files of module matching $1 (wildcard expression)
  236. #
  237. local ex="${1:-*}"
  238. local incdir
  239. echo "$SHELLFU_PATH:$SHELLFU_INCLUDE-sh:$SHELLFU_INCLUDE-$SHELLFU_COMPAT" \
  240. | tr ":" '\n' \
  241. | awk '!seen[$0]++' \
  242. | while read -r incdir; do
  243. shellfu __debug "checking: $incdir"
  244. test -d "$incdir" || continue
  245. shellfu __debug "looking into: $incdir"
  246. find "$incdir" -name "$ex.sh"
  247. done
  248. ;;
  249. _select_mfile)
  250. #
  251. # Select file that contains module named $1
  252. #
  253. shellfu _list_mfiles "$1" | grep -m1 .
  254. ;;
  255. _read_directive)
  256. local s=false
  257. test "x$1" == "x-s" && { s=true; shift; }
  258. local dname=$1
  259. local mpath=$2
  260. local dbody
  261. local dvalue
  262. local usage="_read_directive DIRECTIVE PATH/TO/MODULE.sh"
  263. test -n "$dname" || shellfu __usage "$usage"
  264. test -n "$mpath" || shellfu __usage "$usage"
  265. test -f "$mpath" || shellfu __die "no such file: $mpath"
  266. shellfu __debug "reading directive: '$dname' from '$mpath'"
  267. dbody=$(
  268. { head -3 "$mpath"; tail -3 "$mpath"; } \
  269. | grep '^#shellfu ' \
  270. | tr ' ' '\n' \
  271. | grep -m1 "^$dname=[^ ]*"
  272. )
  273. test -n "$dbody" || {
  274. $s || shellfu __warn "directive not found: '$dname' in '$mpath'"
  275. return 2
  276. }
  277. dvalue=${dbody#$dname=}
  278. echo -n "$dvalue"
  279. test -n "$dvalue"
  280. ;;
  281. *)
  282. shellfu __die "unknown shellfu command: $cmd"
  283. ;;
  284. esac
  285. }