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

sfpi.sh 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. #!/bin/bash
  2. shellfu import sfdoc
  3. shellfu import pretty
  4. #
  5. # Shellfu plugin interface
  6. #
  7. # Discover, query, select and run plugins inside subshells so that they
  8. # can't interfere too easily.
  9. #
  10. #
  11. # EXAMPLE PLUGIN SETUP
  12. # ====================
  13. #
  14. # Have a Shellfu module 'foo` wanting to support plugins `bar` and
  15. # `baz`. We'll need to implement 3 modules: `foo` itself and one
  16. # for each plugin: `_foop_bar` and `_foop_baz`.
  17. #
  18. # For example, the pluging `bar` might look like:
  19. #
  20. # _foop_bar__process() {
  21. # #
  22. # # Actual "payload" function
  23. # #
  24. # do_something_meaningful_with "$@"
  25. # }
  26. #
  27. # _foop_bar__sfpimeta() {
  28. # #
  29. # # Get self meta-data key $1
  30. # #
  31. # local key=$1
  32. # case $key in
  33. # desc) echo "We do meaningful thing in terms of bar." ;;
  34. # esac
  35. # }
  36. #
  37. # _foop_bar__sfpi_compat() {
  38. # #
  39. # # Check if we can function in this environment
  40. # #
  41. # test -d /etc/bar
  42. # }
  43. #
  44. # The useful function is _foop_bar__process(), which does the actual thing
  45. # that the plugin is supposed to do. You can have at least one such function,
  46. # for obvious reasons.
  47. #
  48. # There are two optional function helping with the 'sfpi' discovery:
  49. #
  50. # The _foop_bar__sfpimeta() function enables parent query plugin meta-data
  51. # using sfpi__key() function. Meanings of individual keys are not defined.
  52. #
  53. # The _foop_bar__sfpi_compat() function allows parent let plugin decide if
  54. # it's compatible with current environment and prevent it from being loaded.
  55. # In other words, if this function returns 1, the plugin would not be listed
  56. # by sfpi__ls().
  57. #
  58. # The minimal foo.sh in this example could look like:
  59. #
  60. # SFPI__PREFIX=_foop # you NEED to set this ASAP
  61. #
  62. # foo() {
  63. # local stuff=$1
  64. # for plugin in $(sfpi__ls); do
  65. # sfpi__call "$plugin" process "$stuff"
  66. # done
  67. # }
  68. #
  69. #
  70. # Plugin name prefix
  71. #
  72. # In order for your plugins to be discoverable, name them with this
  73. # prefix. Common naming scheme is '_<APPNAME>p_<PLNAME>', i.e. if
  74. # your application was named `foo`, final layout could look something
  75. # like this:
  76. #
  77. # foo
  78. # _foop_bar
  79. # _foop_baz
  80. #
  81. # where you could say that your module has plugins `foo` and `bar`. In
  82. # the above example, value of $SFPI__PREFIX would be `_foop` (starting
  83. # with underscore to remain hidden in normal listings by *sfdoc*.
  84. #
  85. SFPI__PREFIX=
  86. sfpi__call() {
  87. #
  88. # Call plugin $1 function $2 in subshell
  89. #
  90. __sfpi__ckpfx
  91. local plg=$1; shift
  92. local fun=$1; shift
  93. (
  94. shellfu import "${SFPI__PREFIX}_${plg}"
  95. type -t "${SFPI__PREFIX}_${plg}__$fun" >/dev/null || {
  96. warn "plugin function not implemented: $fun in $plg"
  97. exit 3
  98. }
  99. "${SFPI__PREFIX}_${plg}__$fun" "$@"
  100. )
  101. }
  102. sfpi__import() {
  103. #
  104. # Import plugin $1 into current namespace
  105. #
  106. __sfpi__ckpfx
  107. local plg=$1
  108. local mod="${SFPI__PREFIX}_${plg}"
  109. shellfu try_import "$mod" || {
  110. warn "module for plugin not found: $mod"
  111. return 3
  112. }
  113. sfpi__is_compat "$plg" || {
  114. warn "plugin not compatible: $mod"
  115. return 3
  116. }
  117. shellfu import "$mod"
  118. }
  119. sfpi__varname() {
  120. #
  121. # Print name of plugin $1 variable $2
  122. #
  123. # Note that the variables are assumed to be named in ALL_CAPS,
  124. # so prefix is converted accordingly (not the variable name,
  125. # though).
  126. #
  127. # Example:
  128. #
  129. # SFPI__PREFIX=foo
  130. # sfpi__varname bar BAZ # prints 'FOO_BAR__BAZ'
  131. #
  132. __sfpi__ckpfx
  133. local plg=$1
  134. local var=$2
  135. echo "${SFPI__PREFIX^^}_${plg^^}__$var"
  136. }
  137. sfpi__varvalue() {
  138. #
  139. # Print value of plugin $1 variable $2
  140. #
  141. # Note that the variables are assumed to be named in ALL_CAPS,
  142. # so prefix is converted accordingly (not the variable name,
  143. # though).
  144. #
  145. # Example:
  146. #
  147. # SFPI__PREFIX=foo
  148. # sfpi__varvalue bar BAZ # prints value of $FOO_BAR__BAZ
  149. #
  150. __sfpi__ckpfx
  151. local plg=$1
  152. local var=$2
  153. local varname="${SFPI__PREFIX^^}_${plg^^}__$var"
  154. echo "${!varname}"
  155. }
  156. sfpi__funname() {
  157. #
  158. # Print name of plugin $1 function $2
  159. #
  160. __sfpi__ckpfx
  161. local plg=$1
  162. local fun=$2
  163. echo "${SFPI__PREFIX}_${plg}__$fun"
  164. }
  165. sfpi__modname() {
  166. #
  167. # Print name of module implementing plugin $1
  168. #
  169. __sfpi__ckpfx
  170. local plg=$1
  171. echo "${SFPI__PREFIX}_${plg}"
  172. }
  173. sfpi__has() {
  174. #
  175. # True if plugin $1 has function $2
  176. #
  177. __sfpi__ckpfx
  178. local mod="${SFPI__PREFIX}_$1"
  179. local fun=$2
  180. (
  181. shellfu import "$mod"
  182. type -t "${mod}__$fun" >/dev/null;
  183. )
  184. }
  185. sfpi__is_compat() {
  186. #
  187. # True if plugin $1 is compatible
  188. #
  189. # Load plugin in subshell and look for compatibility function. If
  190. # such function exists, it's called without arguments and if exit
  191. # status is zero, plugin is compatible.
  192. #
  193. # If plugin has no compatibility function, it's considered
  194. # compatible.
  195. #
  196. __sfpi__ckpfx
  197. local plg=$1
  198. local mod="${SFPI__PREFIX}_$plg"
  199. local fun=${mod}__sfpi_compat
  200. (
  201. shellfu import "$mod" || exit 3
  202. sfpi__has "$plg" sfpi_compat || exit 0
  203. "$fun"; es=$?
  204. test $es -gt 1 && warn "illegal exit status: $es from ${fun}"
  205. return $es
  206. )
  207. }
  208. sfpi__key() {
  209. #
  210. # Safely get meta-data key $2 from plugin for item type $1
  211. #
  212. __sfpi__ckpfx
  213. local plg=$1 # plugin name
  214. local key=$2 # meta-data key
  215. local value # ^^ value
  216. local fun="${SFPI__PREFIX}_${plg}__sfpimeta" # plg getter function name
  217. (
  218. sfpi__import "$plg"
  219. if type -t "$fun" >/dev/null; then
  220. value=$("$fun" "$key")
  221. debug -v fun key value
  222. test -n "$value" || value="(none)"
  223. else
  224. value='(undefined)'
  225. fi
  226. echo "$value"
  227. )
  228. }
  229. sfpi__ls_all() {
  230. #
  231. # List all available plugins
  232. #
  233. __sfpi__ckpfx
  234. SFDOC_SHOW_HIDDEN=true sfdoc__ls_m \
  235. | grep "^$SFPI__PREFIX"'_[[:alpha:]][[:alnum:]]*$' \
  236. | sed "s/^${SFPI__PREFIX}_//"
  237. }
  238. sfpi__ls() {
  239. #
  240. # List compatible plugins
  241. #
  242. __sfpi__ckpfx
  243. local plg
  244. for plg in $(sfpi__ls_all); do
  245. sfpi__is_compat "$plg" && echo "$plg"
  246. done
  247. true
  248. }
  249. __sfpi__ckpfx() {
  250. #
  251. # Check if $SFPI__PREFIX has been set
  252. #
  253. debug -v SFPI__PREFIX
  254. test -n "$SFPI__PREFIX" \
  255. || die "bug: need to set SFPI__PREFIX first"
  256. }