123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- #!/bin/bash
-
- shellfu import sfdoc
- shellfu import pretty
-
- #
- # Shellfu plugin interface
- #
- # Discover, query, select and run plugins inside subshells so that they
- # can't interfere too easily.
- #
- #
- # EXAMPLE PLUGIN SETUP
- # ====================
- #
- # Have a Shellfu module 'foo` wanting to support plugins `bar` and
- # `baz`. We'll need to implement 3 modules: `foo` itself and one
- # for each plugin: `_foop_bar` and `_foop_baz`.
- #
- # For example, the pluging `bar` might look like:
- #
- # _foop_bar__process() {
- # #
- # # Actual "payload" function
- # #
- # do_something_meaningful_with "$@"
- # }
- #
- # _foop_bar__sfpimeta() {
- # #
- # # Get self meta-data key $1
- # #
- # local key=$1
- # case $key in
- # desc) echo "We do meaningful thing in terms of bar." ;;
- # esac
- # }
- #
- # _foop_bar__sfpi_compat() {
- # #
- # # Check if we can function in this environment
- # #
- # test -d /etc/bar
- # }
- #
- # The useful function is _foop_bar__process(), which does the actual thing
- # that the plugin is supposed to do. You can have at least one such function,
- # for obvious reasons.
- #
- # There are two optional function helping with the 'sfpi' discovery:
- #
- # The _foop_bar__sfpimeta() function enables parent query plugin meta-data
- # using sfpi__key() function. Meanings of individual keys are not defined.
- #
- # The _foop_bar__sfpi_compat() function allows parent let plugin decide if
- # it's compatible with current environment and prevent it from being loaded.
- # In other words, if this function returns 1, the plugin would not be listed
- # by sfpi__ls().
- #
- # The minimal foo.sh in this example could look like:
- #
- # SFPI__PREFIX=_foop # you NEED to set this ASAP
- #
- # foo() {
- # local stuff=$1
- # for plugin in $(sfpi__ls); do
- # sfpi__call "$plugin" process "$stuff"
- # done
- # }
- #
-
- #
- # Plugin name prefix
- #
- # In order for your plugins to be discoverable, name them with this
- # prefix. Common naming scheme is '_<APPNAME>p_<PLNAME>', i.e. if
- # your application was named `foo`, final layout could look something
- # like this:
- #
- # foo
- # _foop_bar
- # _foop_baz
- #
- # where you could say that your module has plugins `foo` and `bar`. In
- # the above example, value of $SFPI__PREFIX would be `_foop` (starting
- # with underscore to remain hidden in normal listings by *sfdoc*.
- #
- SFPI__PREFIX=
-
- sfpi__call() {
- #
- # Call plugin $1 function $2 in subshell
- #
- __sfpi__ckpfx
- local plg=$1; shift
- local fun=$1; shift
- (
- shellfu import "${SFPI__PREFIX}_${plg}"
- type -t "${SFPI__PREFIX}_${plg}__$fun" >/dev/null || {
- warn "plugin function not implemented: $fun in $plg"
- exit 3
- }
- "${SFPI__PREFIX}_${plg}__$fun" "$@"
- )
- }
-
- sfpi__import() {
- #
- # Import plugin $1 into current namespace
- #
- __sfpi__ckpfx
- local plg=$1
- local mod="${SFPI__PREFIX}_${plg}"
- shellfu try_import "$mod" || {
- warn "module for plugin not found: $mod"
- return 3
- }
- sfpi__is_compat "$plg" || {
- warn "plugin not compatible: $mod"
- return 3
- }
- shellfu import "$mod"
- }
-
- sfpi__varname() {
- #
- # Print name of plugin $1 variable $2
- #
- # Note that the variables are assumed to be named in ALL_CAPS,
- # so prefix is converted accordingly (not the variable name,
- # though).
- #
- # Example:
- #
- # SFPI__PREFIX=foo
- # sfpi__varname bar BAZ # prints 'FOO_BAR__BAZ'
- #
- __sfpi__ckpfx
- local plg=$1
- local var=$2
- echo "${SFPI__PREFIX^^}_${plg^^}__$var"
- }
-
- sfpi__varvalue() {
- #
- # Print value of plugin $1 variable $2
- #
- # Note that the variables are assumed to be named in ALL_CAPS,
- # so prefix is converted accordingly (not the variable name,
- # though).
- #
- # Example:
- #
- # SFPI__PREFIX=foo
- # sfpi__varvalue bar BAZ # prints value of $FOO_BAR__BAZ
- #
- __sfpi__ckpfx
- local plg=$1
- local var=$2
- local varname="${SFPI__PREFIX^^}_${plg^^}__$var"
- echo "${!varname}"
- }
-
- sfpi__funname() {
- #
- # Print name of plugin $1 function $2
- #
- __sfpi__ckpfx
- local plg=$1
- local fun=$2
- echo "${SFPI__PREFIX}_${plg}__$fun"
- }
-
- sfpi__modname() {
- #
- # Print name of module implementing plugin $1
- #
- __sfpi__ckpfx
- local plg=$1
- echo "${SFPI__PREFIX}_${plg}"
- }
-
- sfpi__has() {
- #
- # True if plugin $1 has function $2
- #
- __sfpi__ckpfx
- local mod="${SFPI__PREFIX}_$1"
- local fun=$2
- (
- shellfu import "$mod"
- type -t "${mod}__$fun" >/dev/null;
- )
- }
-
- sfpi__is_compat() {
- #
- # True if plugin $1 is compatible
- #
- # Load plugin in subshell and look for compatibility function. If
- # such function exists, it's called without arguments and if exit
- # status is zero, plugin is compatible.
- #
- # If plugin has no compatibility function, it's considered
- # compatible.
- #
- __sfpi__ckpfx
- local plg=$1
- local mod="${SFPI__PREFIX}_$plg"
- local fun=${mod}__sfpi_compat
- (
- shellfu import "$mod" || exit 3
- sfpi__has "$plg" sfpi_compat || exit 0
- "$fun"; es=$?
- test $es -gt 1 && warn "illegal exit status: $es from ${fun}"
- return $es
- )
- }
-
- sfpi__key() {
- #
- # Safely get meta-data key $2 from plugin for item type $1
- #
- __sfpi__ckpfx
- local plg=$1 # plugin name
- local key=$2 # meta-data key
- local value # ^^ value
- local fun="${SFPI__PREFIX}_${plg}__sfpimeta" # plg getter function name
- (
- sfpi__import "$plg"
- if type -t "$fun" >/dev/null; then
- value=$("$fun" "$key")
- debug -v fun key value
- test -n "$value" || value="(none)"
- else
- value='(undefined)'
- fi
- echo "$value"
- )
- }
-
- sfpi__ls_all() {
- #
- # List all available plugins
- #
- __sfpi__ckpfx
- SFDOC_SHOW_HIDDEN=true sfdoc__ls_m \
- | grep "^$SFPI__PREFIX"'_[[:alpha:]][[:alnum:]]*$' \
- | sed "s/^${SFPI__PREFIX}_//"
- }
-
- sfpi__ls() {
- #
- # List compatible plugins
- #
- __sfpi__ckpfx
- local plg
- for plg in $(sfpi__ls_all); do
- sfpi__is_compat "$plg" && echo "$plg"
- done
- true
- }
-
- __sfpi__ckpfx() {
- #
- # Check if $SFPI__PREFIX has been set
- #
- debug -v SFPI__PREFIX
- test -n "$SFPI__PREFIX" \
- || die "bug: need to set SFPI__PREFIX first"
- }
|