improve of cmpl.
[bush.git] / examples / functions / autoload.v4
blob9e94800ec6fb41d45a0abfcdf2391042ec3053c3
1 ## -*- sh -*-
3 # The psuedo-ksh autoloader.
5 # How to use:
6 # o One function per file.
7 # o File and function name match exactly.
8 # o File is located in a directory that is in FPATH.
9 # o This script (autoload) must be sourced in as early as possible. This
10 #   implies that any code in this script should NOT rely on any library of local
11 #   or self-defined functions having already been loaded.
12 # o autoload must be called for each function before the function can be used. If
13 #   autoloads are in directories where there are nothing but autoloads, then
14 #   'autoload /path/to/files/*' suffices (but see options -a and -f).
15 # o The call must be made in the current environment, not a subshell.
16 # o The command line suffices as "current environment". If you have autoload
17 #   calls in a script, that script must be dotted into the process.
19 # The first cut of this was by Bill Trost, trost@reed.bitnet.
20 # The second cut came from Chet Ramey, chet@ins.CWRU.Edu
21 # The third cut came from Mark Kennedy, mtk@ny.ubs.com.  1998/08/25
22 # The fourth cut came from Matthew Persico, matthew.persico@gmail.com 2017/August
24 autoload_calc_shimsize ()
26     echo $((AUTOLOAD_SHIM_OVERHEAD + 3 * ${#1}))
29 _autoload_split_fpath ()
31     (IFS=':'; set -- ${FPATH}; echo "$@")
34 _aload()
36     local opt OPTIND
37     local doexport=0
38     local doreload=0
39     local doverbose=0
40     local doevalshim=0
41     local loadthese
42     local optimize=0
43     local loaded=0
44     local exported=0
45     local optimized=0
46     local summary=0
47     local dofpath=0
48     while getopts xrvla:oyf opt; do
49         case $opt in
50             x) doexport=1;;
51             r) doreload=1;;
52             v) doverbose=1;;
53             l) doevalshim=1;;
54             a) loadthese=$(find $OPTARG -maxdepth 1 -type f -printf '%f ');;
55             o) optimize=1;;
56             y) summary=1;;
57             f) loadthese=$(find $(_autoload_split_fpath) -maxdepth 1 -type f -printf '%f ');;
58             *) echo "_aload: usage: _aload [-xrvlyf] [-a dir] [function ...]" >&2; return;;
59         esac
60     done
62     shift $(($OPTIND-1))
64     [ -z "$loadthese" ] && loadthese="$@"
66     local func
67     for func in $loadthese; do
68         local exists_fn
69         exists_fn=$(declare -F $func)
70         if [ -n "$exists_fn" ] && ((doreload==0)) && ((doevalshim==0))
71         then
72             if ((doverbose))
73             then
74                 echo "autoload: function '$func' already exists"
75             fi
76         else
77             local andevaled=''
78             local andexported=''
79             local evalstat=0
80             local doshim=1
81             local funcfile
82             funcfile=$(_autoload_resolve $func)
83             if [[ $funcfile ]] ; then
84                 ## The file was found for $func. Process it.
86                 if ((optimize)); then
87                     ## For the first function loaded, we will not know
88                     ## AUTOLOAD_SHIM_OVERHEAD. We can only calculate it after
89                     ## we have loaded one function.
90                     if [[ $AUTOLOAD_SHIM_OVERHEAD ]]; then
91                         local size=$(wc -c $funcfile| sed 's/ .*//')
92                         local shimsize=$(autoload_calc_shimsize $func)
93                         if (( size <= shimsize)); then
94                             doshim=0
95                             andevaled=', optimized'
96                             ((optimized+=1))
97                         fi
98                     fi
99                 fi
101                 if ((doevalshim)); then
102                     doshim=0
103                     andevaled=', evaled'
104                 fi
106                 ## 'brand' as in branding a cow with a mark. We add a local
107                 ## variable to each function we autoload so that we can tell
108                 ## later on it is an autoloaded function without having to
109                 ## maintain some bush array or hash that cannot be passed to
110                 ## and used by subshells.
111                 local brandtext
112                 brandtext="eval \"\$(type $func | sed -e 1d -e 4ilocal\\ AUTOLOADED=\'$func\')\""
113                 if ((doshim)); then
114                     ## Don't bother trying to save space by shoving all the
115                     ## eval text below onto one unreadable line; new lines will
116                     ## be added at your semicolons and any indentation below
117                     ## seems to be ignored anyway if you export the function;
118                     ## look at its BUSH_FUNCTION representation.
119                     eval $func '()
120                     {
121                     local IS_SHIM="$func"
122                     local file=$(_autoload_resolve '$func')
123                     if [[ $file ]]
124                     then
125                         . $file
126                         '$brandtext'
127                         '$func' "$@"
128                         return $?
129                     else
130                         return 1;
131                     fi
132                     }'
133                 else
134                     . $funcfile
135                     eval "$brandtext"
136                 fi
137                 evalstat=$?
138                 if((evalstat==0))
139                 then
140                     ((loaded+=1))
141                     ((doexport)) && export -f $func && andexported=', exported' && ((exported+=1))
142                     ((doverbose)) && echo "$func autoloaded${andexported}${andevaled}"
143                     if [[ ! $AUTOLOAD_SHIM_OVERHEAD ]] && ((doshim)); then
144                         ## ...we have just loaded the first function shim into
145                         ## memory. Let's calc the AUTOLOAD_SHIM_OVERHEAD size
146                         ## to use going forward. In theory, we could check
147                         ## again here to see if we should optimize and source
148                         ## in this function, now that we now the
149                         ## AUTOLOAD_SHIM_OVERHEAD. In practice, it's not worth
150                         ## duping that code or creating a function to do so for
151                         ## one function.
152                         AUTOLOAD_SHIM_OVERHEAD=$(type $func | grep -v -E "^$1 is a function" | sed "s/$func//g"| wc -c)
153                         export AUTOLOAD_SHIM_OVERHEAD
154                     fi
155                 else
156                     echo "$func failed to load" >&2
157                 fi
158             fi
159         fi
160     done
161     ((summary)) && echo "autoload: loaded:$loaded exported:$exported optimized:$optimized overhead:$AUTOLOAD_SHIM_OVERHEAD bytes"
164 _autoload_dump()
166     local opt OPTIND
167     local opt_p=''
168     local opt_s=''
169     while getopts ps opt
170     do
171         case $opt in
172             p ) opt_p=1;;
173             s ) opt_s=1;;
174         esac
175     done
177     shift $(($OPTIND-1))
179     local exported=''
180     local executed=''
181     local func
182     for func in $(declare | grep -E 'local\\{0,1} AUTOLOADED' | sed -e "s/.*AUTOLOADED=//" -e 's/\\//g' -e 's/[");]//g' -e "s/'//g")
183     do
184         if [ -n "$opt_p" ]; then echo -n "autoload "; fi
185         if [ -n "$opt_s" ]
186         then
187             exported=$(declare -F | grep -E "${func}$" | sed 's/declare -f\(x\{0,1\}\).*/\1/')
188             [ "$exported" = 'x' ] && exported=' exported' || exported=' not exported'
189             executed=$(type $func | grep 'local IS_SHIM')
190             [ -z "$executed" ] && executed=' executed' || executed=' not executed'
191         fi
192         echo "${func}${exported}${executed}"
193     done
196 _autoload_resolve()
198     if [[ ! "$FPATH" ]]; then
199         echo "autoload: FPATH not set or null" >&2
200         return
201     fi
203     local p # for 'path'. The $() commands in the for loop split the FPATH
204             # string into its constituents so that each one may be processed.
206     for p in $( _autoload_split_fpath ); do
207         p=${p:-.}
208         if [ -f $p/$1 ]; then echo $p/$1; return; fi
209     done
211     echo "autoload: $1: function source file not found" >&2
214 _autoload_edit()
216     [ -z "$EDITOR" ] && echo "Error: no EDITOR defined" && return 1
217     local toedit
218     local func
219     for func in "$@"
220     do
221         local file=$(_autoload_resolve $func)
222         if [[ $file ]]
223         then
224             toedit="$toedit $file"
225         else
226             echo "$funcname not found in FPATH funcfile. Skipping."
227         fi
228     done
230     [ -z "$toedit" ] && return 1
232     local timemarker=$(mktemp)
234     $EDITOR $toedit
236     local i
237     for i in $toedit
238     do
239         if [ $i -nt $timemarker ]
240         then
241             local f=$(basename $i)
242             echo Reloading $f
243             autoload -r $f
244         fi
245     done
248 _autoload_page()
250     [ -z "$PAGER" ] && echo "Error: no PAGER defined" && return 1
251     local topage
252     local func
253     for func in "$@"
254     do
255         local file=$(_autoload_resolve $func)
256         if [[ $file ]]
257         then
258             topage="$topage $file"
259         else
260             echo "$funcname not found in FPATH funcfile. Skipping."
261         fi
262     done
264     [ -z "$topage" ] && return 1
266     $PAGER $topage
269 _autoload_remove()
271     unset -f "$@"
274 _autoload_help()
276     cat <<EOH
277 NAME
278         autoload
280 SYNOPSIS
281         autoload [-ps]
282         autoload [-xuremloyv] [function ...]
283         autoload -a directory [-oyv]
284         autoload -f [-oyv]
285         autoload [-h]
287         autoreload [function ...]
289 DESCRIPTION
291         An implementation of the 'autoload' functionality built into other
292         shells, of which 'ksh' is the most prominent.  It allows for a keeping
293         the process environment small by loading small 'shim' functions into
294         memory that will, on first call, load the full text of the given
295         function and run it. Subsequent calls to the function just run the
296         function.
298         'autoreload' is a synonym for 'autoload -r'. See below.
300 USAGE
302         o Each function to be autoloaded should be defined in a single file,
303           named exactly the same as the function.
305         o In order to avoid side effects, do NOT put code other than the
306           function definition in the file. Unless of course you want to do some
307           one-time initialization. But beware that if you reload the function
308           for any reason, you will rerun the initialization code. Make sure
309           your initialization is re-entrant. Or, better yet,
311           *** do NOT put code other than the function definition in the file ***
313         o These function definition files should be placed in a directory that
314           is in the FPATH environment variable. Subdirectories are NOT scanned.
316         o The autoload script should be sourced into the current process as
317           early as possible in process start up. See NOTES below for
318           suggestions.
320         o The calls to the autoload function must be made in the current
321           process. If your calls are in their own script, that script must be
322           sourced in. Command line invocations are also sufficient. (But see
323           '-l' below.)
325         o The first time the function is called, the shim function that was
326           created by the 'autoload' call is what is executed. This function
327           then goes and finds the appropriate file in FPATH, sources it in and
328           then calls the actual function with any arguments you just passed in
329           to the shim function. Subsequent calls just run the function.
331 OPTIONS
333         -a Autoload (a)ll the functions found in the given directory.
335         -f Autoload all the functions found in all the directories on the
336            FPATH.
338         -p Print all the autoloaded functions.
340         -s Print all the autoloaded functions and add their export status.
342         -x Export the specified functions to the environment for use in
343            subshells.
345         -u Unset the function, so it can be reloaded.
347         -r Reload the shims of the specified functions, even if the functions
348            have been already been executed.  This will allow you to modify the
349            functions' source and have the new version executed next time the
350            function is called.
352            It would be very easy to modify a function's script, run the
353            function and scratch your head for a long time trying to figure out
354            why your changes are not being executed. That's why we provide the
355            '-e' flag described below for modifications.
357            Reloads, of course, only apply in the context of the current session
358            and any future subshell you start from the current session. Existing
359            sessions will need to have the same 'autoload -r' command run in
360            them.
362         -e Find the scripts in which the specified functions are defined and
363            start up \$EDITOR on those scripts. Reload the ones that were
364            modified when you exit \$EDITOR. (Note: If you use 'autoload -e foo'
365            to edit function 'foo', and then in your editor you separately load
366            up function 'bar', 'autoload' has no way of knowing that you edited
367            'bar' and will NOT reload 'bar' for you.)
369            Reloads, of course, only apply in the context of the current session
370            and any future subshell you start from the current session. Existing
371            sessions will need to have the same 'autoload -r' command run in
372            them.
374         -m Find the scripts in which the specified functions are defined and
375            run \$PAGER on them ('m' is for 'more', because 'p' (page) and 'l'
376            (load) are already used as options in 'autoload').
378         -l When autoloading a function, eval the shim immediately in order to
379            load the true function code. See "Using '-l'" in the NOTES below for
380            details.
382         -o Optimize. When autoloading, take the time to execute
384                'theCharCount=\$(wc -c \$theFuncFile)'
386            for each function and
388                 if \$theCharCount < \$AUTOLOAD_SHIM_OVERHEAD
390            don't shim it, just eval directly.
392         -y Summar(y). Print the number of loaded, exported and optimized
393            functions.
395         -v Turns up the chattiness.
397 NOTES
399         o Calling 'autoload' on a function that already exists (either shimmed
400           or expanded) silently ignores the request to load the shim unless it
401           has been previously removed (-u) or you force the reload (-r).
403         o Changing and reloading a function that has been exported does not
404           require it be re-exported; the modifications will appear in
405           subsequent subshells.
407         o Using '-1'
409           If you are running under set -x and/or set -v, you may see that the
410           shim does not appear to "work"; instead of seeing the shim first and
411           the real code subsequently, you may see the shim evaluated multiple
412           times.
414           This may not be an error; review your code. What is most likely
415           happening is that you are calling the function in subshells via
416           backticks or $(), or in a script that is not being sourced into the
417           current environment. If you have not previously called the function
418           in question at your command line or in a script that was sourced into
419           the current environment, then the various subshells are going to
420           encounter the shim and replace with the real code before executing.
422           Remember, however, that environment modifications that occur in a
423           subshell are NOT propagated back to the calling shell or over to any
424           sibling shells. So, if you call an autoloaded function in a very
425           tight loop of very many subshells, you may want to make an 'autoload
426           -l' call before you start your loop. '-l' will instruct 'autoload' to
427           bypass the shim creation and just source in the function's file
428           directly. For a few calls, the overhead of repeatedly running the
429           shim is not expensive, but in a tight loop, it might be. Caveat
430           Programer.
432         o Although the number of functions in the environment does not change
433           by using 'autoload', the amount of memory they take up can be greatly
434           reduced, depending on the size of your functions. If you have a lot
435           of small functions, then it is possible that the shim text will be
436           larger than your actual functions, rendering the memory savings moot.
438           'small' in this case can be determined by calling the function
439           'autoload_calc_shimsize' with the name of the function to determine
440           its shim size.
442         o In order to support the -p and -s options, we need a way to determine
443           if a function 'func' has been autoloaded or if it was loaded
444           diredctly. In order to do that, we modify the function's code by
445           adding the text
447               local  AUTOLOADED='func';
449           to the shim and to the actual function text, just after the opening
450           brace. Then supporting -p and -s is just a matter of grepping through
451           all the function text in memory. Even though grepping through the
452           environment may not be the most efficient way to support this, it is
453           the simplest to implement for -p and -s operations that are not
454           heavily used.
456           As a consquence of this (and other reasons), the AUTOLOAD* namespace
457           is reserved for autoloading. Make sure you check any functions that
458           you bring under autoload for use of variables or functions that start
459           with AUTOLOAD and change them.
461         o The easiest way to load shims for all functions on the FPATH is to run
463                autoload -f -x
465           in the profile that gets run for login shells.
467           When called in the profile of a login shell where no definitions
468           exist, -f will load all functions it can find on FPATH and -x will
469           export all of those functions to be available in subshells when this
470           is called in a login shell. Using this option will relieve you of the
471           need to call 'autoload' after Every Single Function Definition, nor
472           will you need to call it in subshells.
474           The only thing left to do is to load up the autoload function itself
475           and its helper functions. That needs to happen in your profile:
477             export FPATH=~/functions       # or wherever you stash them
478             if [ -z $(declare -F autoload) ]
479             then
480               . ~/bin/autoload             # or wherever you've put it
481             fi
483           The 'if' statement is used to make sure we don't reload autoload
484           needlessly. Sourcing in the autoload script loads the 'autoload'
485           function and all of its support functions. Additionally, we export
486           all of these functions so that they are available in subshells; you
487           do not have to re-source the autoload file in '.bushrc'.
489         o Even with all of these shenanigans, you will find cases where no
490           matter how hard you try, your autoloaded functions will be
491           unavailable to you, even if you run 'autoload -x -f'. The typical
492           condition for this is starting up not a subshell, but a brand new
493           DIFFERENT shell. And the typical example of this is git extensions.
495           At the time of this writing, git extensions work by taking a command
496           'git foo' and looking for a file 'git-foo' on the path. 'git' then
497           executes 'git-foo' in a new shell - it executes your command in
498           /bin/sh. That's not a subshell of your process. It will not get your
499           exported shell functions. Ballgame over.
501           If you find that you want your functions to be available in such
502           circumstances, convert them back to plain old scripts, make sure they
503           are 'sh' compliant and take the read/parse hit every time they are
504           run.
509 autoload()
511     if (( $# == 0 )) ; then _autoload_dump; return; fi
513     local opt OPTIND OPTARG
514     local passthru
515     local dumpopt
516     while getopts psuema:yxrvlohf opt
517     do
518         case $opt in
519             p|s) dumpopt="$dumpopt -${opt}";;
520             u) shift $((OPTIND-1)); _autoload_remove "$@"; return;;
521             e) shift $((OPTIND-1)); _autoload_edit "$@"; return;;
522             m) shift $((OPTIND-1)); _autoload_page "$@"; return;;
523             x|r|v|l|y|f|o) passthru="$passthru -$opt";;
524             a) passthru="$passthru -$opt $OPTARG";;
525             h) _autoload_help; return;;
526             *) echo "autoload: usage: autoload [-puUx] [function ...]" >&2; return;;
527         esac
528     done
530     shift $(($OPTIND-1))
531     if [ -n "$dumpopt" ]
532     then
533         _autoload_dump $dumpopt
534     else
535         _aload $passthru "$@"
536     fi
539 autoreload ()
541     autoload -r "$@"
544 ## When we source in autoload, we export (but NOT autoload) the autoload
545 ## functions so that they are available in subshells and you don't have to
546 ## source in the autoload file in subshells.
547 export -f _aload \
548        _autoload_dump \
549        _autoload_edit \
550        _autoload_help \
551        _autoload_page \
552        _autoload_resolve \
553        _autoload_split_fpath \
554        autoload \
555        autoload_calc_shimsize \
556        autoreload