#!/bin/sh #shellcheck disable=SC2039,SC1090 # # Shell compatibility # # This should be the binary name of the shell we're running and is used to # choose where to look for modules # SHELLFU_COMPAT=${SHELLFU_COMPAT:-$(ps -p$$ -ocmd= | cut -d' ' -f1 | rev | cut -d/ -f1 | rev)} # # Our installation directory # SHELLFU_DIR=${SHELLFU_DIR:-__SHELLFU_DIR__} # # Shellfu internals debug mode # SHELLFU_DEBUGINIT=${SHELLFU_DEBUGINIT:-false} # # Target to list imported functions in embedding mode # # In embedding mode, we need to log list of imported # modules in an external file. # SHELLFU_EMBEDTMP=${SHELLFU_EMBEDTMP:-} # # True if running in embedded mode (imports do nothing) # SHELLFU_EMBEDDED=${SHELLFU_EMBEDDED:-false} # # Last git commit hash before build # SHELLFU_GIT_HASH=__MKIT_PROJ_GIT_LASTHASH__ # # Module folder prefix # # See shellfu() documentation for details about module loading. # SHELLFU_INCLUDE=${SHELLFU_INCLUDE:-$SHELLFU_DIR/include} # # Colon separated list of additional paths to search modules # # See shellfu() documentation for details about module loading. # SHELLFU_PATH=${SHELLFU_PATH:-} # # Shellfu version (SemVer 2.0) # SHELLFU_VERSION=__MKIT_PROJ_VERSION__ # # Colon-separated list of already imported modules # __SHELLFU_IMPORTED=${__SHELLFU_IMPORTED:-} shellfu() { # # Shellfu init function # # Usage: # # shellfu ACTION MODULE # # MODULE is selected and acted upon, depending on ACTION: # # * `import` will source the module using shell built-in dot command. # # * `try_import` will try to import the module in a separate environment # and exit with zero on success (it will not modity the current # environment). # # * `version` will look for directive `#shellfu module-version=VER` # and print the VER part. VER must not contain spaces. Exit status # is zero if a non-empty VER has been found. # # The module lookup algorithm triest to find file MODULE.sh in following # directories: # # 1. Any directories in colon-separated list SHELLFU_PATH, # # 2. directory formed by joining SHELLFU_INCLUDE and SHELLFU_COMPAT # with dingle dash, # # 3. directory formed by joining 'sh' and SHELLFU_COMPAT with single # dash. # # for example, following code: # # #!/bin/bash # # . $(sfpath) # # SHELLFU_INCLUDE=/shellfu/include # SHELLFU_PATH=/shellfu/addon1:/shellfu/addon2 # # shellfu import foo # # will import first existing file of these: # # /shellfu/addon1/foo.sh # /shellfu/addon2/foo.sh # /shellfu/include-bash/foo.sh # /shellfu/include-sh/foo.sh # # Note that SHELLFU_COMPAT should be auto-detected by shellfu and should # contain name of current shell binary being executed. This means that # modules can exist in multiple versions for each supported shell, and # when loading, shellfu prefers the shell-specific implementation. # local cmd=$1; shift local msg # debug on import case $SHELLFU_DEBUGINIT:$1 in true:import) shellfu __debug "\$*='$*'" shellfu __debug "SHELLFU_COMPAT='$SHELLFU_COMPAT'" shellfu __debug "SHELLFU_DIR='$SHELLFU_DIR'" shellfu __debug "SHELLFU_DEBUGINIT='$SHELLFU_DEBUGINIT'" shellfu __debug "SHELLFU_EMBEDDED='$SHELLFU_EMBEDDED'" shellfu __debug "SHELLFU_EMBEDTMP='$SHELLFU_EMBEDTMP'" shellfu __debug "SHELLFU_GIT_HASH='$SHELLFU_GIT_HASH'" shellfu __debug "__SHELLFU_IMPORTED='$__SHELLFU_IMPORTED'" shellfu __debug "SHELLFU_INCLUDE='$SHELLFU_INCLUDE'" shellfu __debug "SHELLFU_PATH='$SHELLFU_PATH'" shellfu __debug "SHELLFU_VERSION='$SHELLFU_VERSION'" ;; esac case $cmd in __debug) $SHELLFU_DEBUGINIT || return 0 for msg in "$@"; do echo "shellfu:debug: $msg" >&2 done ;; __die) for msg in "$@"; do echo "shellfu:fatal: $msg" >&2 done test -z "$PS1" && exit 3 return 3 ;; __warn) for msg in "$@"; do echo "shellfu: $msg" >&2 done ;; __usage) echo "shellfu:usage: shellfu $1" >&2 test -z "$PS1" && exit 3 return 3 ;; import) # # Import module named $1 # local mname=$1 shellfu _is_imported "$mname" && return 0 if shellfu _do_import "$mname"; then __SHELLFU_IMPORTED="$__SHELLFU_IMPORTED${__SHELLFU_IMPORTED:+:}$mname" else shellfu __die "cannot import module: $mname" fi ;; reload) # # Reload module named $1 (unsafe!) # local mname=$1 shellfu _is_imported "$mname" || { shellfu __warn "cannot reload; not imported yet: $mname" return 2 } if ! shellfu _do_import "$mname"; then shellfu __die "cannot reload module: $mname" fi ;; try_import) # # Try if module $1 could be imported # local mname=$1 ( shellfu _do_import "$mname" ) ;; version) # # Show version of module named $1 # local mname=$1 local mpath mpath=$(shellfu _select_mfile "$mname") || { shellfu __warn "cannot find module: $mname" return 3 } shellfu __debug "found module file: $mpath" shellfu _read_directive "module-version" "$mpath" ;; ls_imported) # # Print list of modules already imported # echo "$__SHELLFU_IMPORTED" | tr : \\n ;; _do_import) # # Really import module named $1 # $SHELLFU_EMBEDDED && return 0 local mname=$1 local es=4 local mpath shellfu __debug "importing module $mname" mpath=$(shellfu _select_mfile "$mname") || { shellfu __warn "cannot find module: $mname" return 3 } shellfu __debug "found module file: $mpath" bash -n "$mpath" || return 3 . "$mpath" es=$? test -n "$SHELLFU_EMBEDTMP" && echo "$mpath" >> "$SHELLFU_EMBEDTMP" local init=__shellfu_${mname}__init if test "$(command -v "$init")" = "$init"; then shellfu __debug "launching init function: $init" $init es=$? fi return $es ;; _is_imported) # # True if module $1 is already imported # local mname=$1 echo "$__SHELLFU_IMPORTED" | tr : \\n | grep -qxe "$mname" ;; _list_mfiles) # # Find module files of module matching $1 (wildcard expression) # local ex="${1:-*}" local incdir echo "$SHELLFU_PATH:$SHELLFU_INCLUDE-sh:$SHELLFU_INCLUDE-$SHELLFU_COMPAT" \ | tr ":" '\n' \ | awk '!seen[$0]++' \ | while read -r incdir; do shellfu __debug "checking: $incdir" test -d "$incdir" || continue shellfu __debug "looking into: $incdir" find "$incdir" -name "$ex.sh" done ;; _select_mfile) # # Select file that contains module named $1 # shellfu _list_mfiles "$1" | grep -m1 . ;; _read_directive) local s=false test "x$1" == "x-s" && { s=true; shift; } local dname=$1 local mpath=$2 local dbody local dvalue local usage="_read_directive DIRECTIVE PATH/TO/MODULE.sh" test -n "$dname" || shellfu __usage "$usage" test -n "$mpath" || shellfu __usage "$usage" test -f "$mpath" || shellfu __die "no such file: $mpath" shellfu __debug "reading directive: '$dname' from '$mpath'" dbody=$( { head -3 "$mpath"; tail -3 "$mpath"; } \ | grep '^#shellfu ' \ | tr ' ' '\n' \ | grep -m1 "^$dname=[^ ]*" ) test -n "$dbody" || { $s || shellfu __warn "directive not found: '$dname' in '$mpath'" return 2 } dvalue=${dbody#$dname=} echo -n "$dvalue" test -n "$dvalue" ;; *) shellfu __die "unknown shellfu command: $cmd" ;; esac }