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

shellfu.sh.skel 8.6KB

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