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

shellfu.sh.skel 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  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. try_import)
  162. #
  163. # Try if module $1 could be imported
  164. #
  165. local mname=$1
  166. ( shellfu _do_import "$mname" )
  167. ;;
  168. version)
  169. #
  170. # Show version of module named $1
  171. #
  172. local mname=$1
  173. local mpath
  174. mpath=$(shellfu _select_mfile "$mname") || {
  175. shellfu __warn "cannot find module: $mname"
  176. return 3
  177. }
  178. shellfu __debug "found module file: $mpath"
  179. shellfu _read_directive "module-version" "$mpath"
  180. ;;
  181. ls_imported)
  182. #
  183. # Print list of modules already imported
  184. #
  185. echo "$__SHELLFU_IMPORTED" | tr : \\n
  186. ;;
  187. _do_import)
  188. #
  189. # Really import module named $1
  190. #
  191. $SHELLFU_EMBEDDED && return 0
  192. local mname=$1
  193. local es=4
  194. local mpath
  195. shellfu __debug "importing module $mname"
  196. mpath=$(shellfu _select_mfile "$mname") || {
  197. shellfu __warn "cannot find module: $mname"
  198. return 3
  199. }
  200. shellfu __debug "found module file: $mpath"
  201. bash -n "$mpath" || return 3
  202. . "$mpath"
  203. es=$?
  204. test -n "$SHELLFU_EMBEDTMP" && echo "$mpath" >> "$SHELLFU_EMBEDTMP"
  205. local init=__shellfu_${mname}__init
  206. if test "$(command -v "$init")" = "$init"; then
  207. shellfu __debug "launching init function: $init"
  208. $init
  209. es=$?
  210. fi
  211. return $es
  212. ;;
  213. _is_imported)
  214. #
  215. # True if module $1 is already imported
  216. #
  217. local mname=$1
  218. echo "$__SHELLFU_IMPORTED" | tr : \\n | grep -qxe "$mname"
  219. ;;
  220. _list_mfiles)
  221. #
  222. # Find module files of module matching $1 (wildcard expression)
  223. #
  224. local ex="${1:-*}"
  225. local incdir
  226. echo "$SHELLFU_PATH:$SHELLFU_INCLUDE-sh:$SHELLFU_INCLUDE-$SHELLFU_COMPAT" \
  227. | tr ":" '\n' \
  228. | awk '!seen[$0]++' \
  229. | while read -r incdir; do
  230. shellfu __debug "checking: $incdir"
  231. test -d "$incdir" || continue
  232. shellfu __debug "looking into: $incdir"
  233. find "$incdir" -name "$ex.sh"
  234. done
  235. ;;
  236. _select_mfile)
  237. #
  238. # Select file that contains module named $1
  239. #
  240. shellfu _list_mfiles "$1" | grep -m1 .
  241. ;;
  242. _read_directive)
  243. local s=false
  244. test "x$1" == "x-s" && { s=true; shift; }
  245. local dname=$1
  246. local mpath=$2
  247. local dbody
  248. local dvalue
  249. local usage="_read_directive DIRECTIVE PATH/TO/MODULE.sh"
  250. test -n "$dname" || shellfu __usage "$usage"
  251. test -n "$mpath" || shellfu __usage "$usage"
  252. test -f "$mpath" || shellfu __die "no such file: $mpath"
  253. shellfu __debug "reading directive: '$dname' from '$mpath'"
  254. dbody=$(
  255. { head -3 "$mpath"; tail -3 "$mpath"; } \
  256. | grep '^#shellfu ' \
  257. | tr ' ' '\n' \
  258. | grep -m1 "^$dname=[^ ]*"
  259. )
  260. test -n "$dbody" || {
  261. $s || shellfu __warn "directive not found: '$dname' in '$mpath'"
  262. return 2
  263. }
  264. dvalue=${dbody#$dname=}
  265. echo -n "$dvalue"
  266. test -n "$dvalue"
  267. ;;
  268. *)
  269. shellfu __die "unknown shellfu command: $cmd"
  270. ;;
  271. esac
  272. }