## -*- sh -*- # The psuedo-ksh autoloader. # How to use: # o One function per file. # o File and function name match exactly. # o File is located in a directory that is in FPATH. # o This script (autoload) must be sourced in as early as possible. This # implies that any code in this script should NOT rely on any library of local # or self-defined functions having already been loaded. # o autoload must be called for each function before the function can be used. If # autoloads are in directories where there are nothing but autoloads, then # 'autoload /path/to/files/*' suffices (but see options -a and -f). # o The call must be made in the current environment, not a subshell. # o The command line suffices as "current environment". If you have autoload # calls in a script, that script must be dotted into the process. # The first cut of this was by Bill Trost, trost@reed.bitnet. # The second cut came from Chet Ramey, chet@ins.CWRU.Edu # The third cut came from Mark Kennedy, mtk@ny.ubs.com. 1998/08/25 # The fourth cut came from Matthew Persico, matthew.persico@gmail.com 2017/August autoload_calc_shimsize () { echo $((AUTOLOAD_SHIM_OVERHEAD + 3 * ${#1})) } _autoload_split_fpath () { (IFS=':'; set -- ${FPATH}; echo "$@") } _aload() { local opt OPTIND local doexport=0 local doreload=0 local doverbose=0 local doevalshim=0 local loadthese local optimize=0 local loaded=0 local exported=0 local optimized=0 local summary=0 local dofpath=0 while getopts xrvla:oyf opt; do case $opt in x) doexport=1;; r) doreload=1;; v) doverbose=1;; l) doevalshim=1;; a) loadthese=$(find $OPTARG -maxdepth 1 -type f -printf '%f ');; o) optimize=1;; y) summary=1;; f) loadthese=$(find $(_autoload_split_fpath) -maxdepth 1 -type f -printf '%f ');; *) echo "_aload: usage: _aload [-xrvlyf] [-a dir] [function ...]" >&2; return;; esac done shift $(($OPTIND-1)) [ -z "$loadthese" ] && loadthese="$@" local func for func in $loadthese; do local exists_fn exists_fn=$(declare -F $func) if [ -n "$exists_fn" ] && ((doreload==0)) && ((doevalshim==0)) then if ((doverbose)) then echo "autoload: function '$func' already exists" fi else local andevaled='' local andexported='' local evalstat=0 local doshim=1 local funcfile funcfile=$(_autoload_resolve $func) if [[ $funcfile ]] ; then ## The file was found for $func. Process it. if ((optimize)); then ## For the first function loaded, we will not know ## AUTOLOAD_SHIM_OVERHEAD. We can only calculate it after ## we have loaded one function. if [[ $AUTOLOAD_SHIM_OVERHEAD ]]; then local size=$(wc -c $funcfile| sed 's/ .*//') local shimsize=$(autoload_calc_shimsize $func) if (( size <= shimsize)); then doshim=0 andevaled=', optimized' ((optimized+=1)) fi fi fi if ((doevalshim)); then doshim=0 andevaled=', evaled' fi ## 'brand' as in branding a cow with a mark. We add a local ## variable to each function we autoload so that we can tell ## later on it is an autoloaded function without having to ## maintain some bash array or hash that cannot be passed to ## and used by subshells. local brandtext brandtext="eval \"\$(type $func | sed -e 1d -e 4ilocal\\ AUTOLOADED=\'$func\')\"" if ((doshim)); then ## Don't bother trying to save space by shoving all the ## eval text below onto one unreadable line; new lines will ## be added at your semicolons and any indentation below ## seems to be ignored anyway if you export the function; ## look at its BASH_FUNCTION representation. eval $func '() { local IS_SHIM="$func" local file=$(_autoload_resolve '$func') if [[ $file ]] then . $file '$brandtext' '$func' "$@" return $? else return 1; fi }' else . $funcfile eval "$brandtext" fi evalstat=$? if((evalstat==0)) then ((loaded+=1)) ((doexport)) && export -f $func && andexported=', exported' && ((exported+=1)) ((doverbose)) && echo "$func autoloaded${andexported}${andevaled}" if [[ ! $AUTOLOAD_SHIM_OVERHEAD ]] && ((doshim)); then ## ...we have just loaded the first function shim into ## memory. Let's calc the AUTOLOAD_SHIM_OVERHEAD size ## to use going forward. In theory, we could check ## again here to see if we should optimize and source ## in this function, now that we now the ## AUTOLOAD_SHIM_OVERHEAD. In practice, it's not worth ## duping that code or creating a function to do so for ## one function. AUTOLOAD_SHIM_OVERHEAD=$(type $func | grep -v -E "^$1 is a function" | sed "s/$func//g"| wc -c) export AUTOLOAD_SHIM_OVERHEAD fi else echo "$func failed to load" >&2 fi fi fi done ((summary)) && echo "autoload: loaded:$loaded exported:$exported optimized:$optimized overhead:$AUTOLOAD_SHIM_OVERHEAD bytes" } _autoload_dump() { local opt OPTIND local opt_p='' local opt_s='' while getopts ps opt do case $opt in p ) opt_p=1;; s ) opt_s=1;; esac done shift $(($OPTIND-1)) local exported='' local executed='' local func for func in $(declare | grep -E 'local\\{0,1} AUTOLOADED' | sed -e "s/.*AUTOLOADED=//" -e 's/\\//g' -e 's/[");]//g' -e "s/'//g") do if [ -n "$opt_p" ]; then echo -n "autoload "; fi if [ -n "$opt_s" ] then exported=$(declare -F | grep -E "${func}$" | sed 's/declare -f\(x\{0,1\}\).*/\1/') [ "$exported" = 'x' ] && exported=' exported' || exported=' not exported' executed=$(type $func | grep 'local IS_SHIM') [ -z "$executed" ] && executed=' executed' || executed=' not executed' fi echo "${func}${exported}${executed}" done } _autoload_resolve() { if [[ ! "$FPATH" ]]; then echo "autoload: FPATH not set or null" >&2 return fi local p # for 'path'. The $() commands in the for loop split the FPATH # string into its constituents so that each one may be processed. for p in $( _autoload_split_fpath ); do p=${p:-.} if [ -f $p/$1 ]; then echo $p/$1; return; fi done echo "autoload: $1: function source file not found" >&2 } _autoload_edit() { [ -z "$EDITOR" ] && echo "Error: no EDITOR defined" && return 1 local toedit local func for func in "$@" do local file=$(_autoload_resolve $func) if [[ $file ]] then toedit="$toedit $file" else echo "$funcname not found in FPATH funcfile. Skipping." fi done [ -z "$toedit" ] && return 1 local timemarker=$(mktemp) $EDITOR $toedit local i for i in $toedit do if [ $i -nt $timemarker ] then local f=$(basename $i) echo Reloading $f autoload -r $f fi done } _autoload_page() { [ -z "$PAGER" ] && echo "Error: no PAGER defined" && return 1 local topage local func for func in "$@" do local file=$(_autoload_resolve $func) if [[ $file ]] then topage="$topage $file" else echo "$funcname not found in FPATH funcfile. Skipping." fi done [ -z "$topage" ] && return 1 $PAGER $topage } _autoload_remove() { unset -f "$@" } _autoload_help() { cat <&2; return;; esac done shift $(($OPTIND-1)) if [ -n "$dumpopt" ] then _autoload_dump $dumpopt else _aload $passthru "$@" fi } autoreload () { autoload -r "$@" } ## When we source in autoload, we export (but NOT autoload) the autoload ## functions so that they are available in subshells and you don't have to ## source in the autoload file in subshells. export -f _aload \ _autoload_dump \ _autoload_edit \ _autoload_help \ _autoload_page \ _autoload_resolve \ _autoload_split_fpath \ autoload \ autoload_calc_shimsize \ autoreload