]>
Commit | Line | Data |
---|---|---|
ccc6cda3 JA |
1 | # require.bash |
2 | # Author: Noah Friedman <friedman@prep.ai.mit.edu> | |
3 | # Created: 1992-07-08 | |
4 | # Last modified: 1993-09-29 | |
5 | # Public domain | |
6 | ||
7 | # Commentary: | |
8 | ||
9 | # These functions provide an interface based on the lisp implementation for | |
10 | # loading libraries when they are needed and eliminating redundant loading. | |
11 | # The basic idea is that each "package" (or set of routines, even if it is | |
12 | # only one function) registers itself with a symbol that marks a "feature" | |
13 | # as being "provided". If later you "require" a given feature, you save | |
14 | # yourself the trouble of explicitly loading it again. | |
15 | # | |
16 | # At the bottom of each package, put a "provide foobar", so when another | |
17 | # package has a "require foobar", it gets loaded and registered as a | |
18 | # "feature" that won't need to get loaded again. (See warning below for | |
19 | # reasons why provide should be put at the end.) | |
20 | # | |
21 | # The list of provided features are kept in the `FEATURES' variable, which | |
22 | # is not exported. Care should be taken not to munge this in the shell. | |
23 | # The search path comes from a colon-separated `FPATH' variable. It has no | |
24 | # default value and must be set by the user. | |
25 | # | |
26 | # Require uses `fpath_search', which works by scanning all of FPATH for a | |
27 | # file named the same as the required symbol but with a `.bash' appended to | |
28 | # the name. If that is found, it is loaded. If it is not, FPATH is | |
29 | # searched again for a file name the same as the feature (i.e. without any | |
30 | # extension). Fpath_search may be useful for doing library filename | |
31 | # lookups in other functions (such as a `load' or `autoload' function). | |
32 | # | |
33 | # Warning: Because require ultimately uses the builtin `source' command to | |
34 | # read in files, it has no way of undoing the commands contained in the | |
35 | # file if there is an error or if no provide statement appeared (this | |
36 | # differs from the lisp implementation of require, which normally undoes | |
37 | # most of the forms that were loaded if the require fails). Therefore, to | |
38 | # minize the number of problems caused by requiring a faulty package (such | |
39 | # as syntax errors in the source file) it is better to put the provide at | |
40 | # the end of the file, rather than at the beginning. | |
41 | ||
42 | # Code: | |
43 | ||
44 | # Exporting this variable would cause considerable lossage, since none of | |
45 | # the functions are exported (or at least, they're not guaranteed to be) | |
46 | export -n FEATURES | |
47 | ||
48 | #:docstring \f: | |
49 | # Null function. Provided only so that one can put page breaks in source | |
50 | # files without any ill effects. | |
51 | #:end docstring: | |
52 | # | |
53 | # (\\014 == C-l) | |
54 | eval "function $(echo -e \\014) () { : }" | |
55 | ||
56 | ||
57 | #:docstring featurep: | |
58 | # Usage: featurep argument | |
59 | # | |
60 | # Returns 0 (true) if argument is a provided feature. Returns 1 (false) | |
61 | # otherwise. | |
62 | #:end docstring: | |
63 | ||
64 | ###;;;autoload | |
65 | function featurep () | |
66 | { | |
67 | local feature="$1" | |
68 | ||
69 | case " ${FEATURES} " in | |
70 | *" ${feature} "* ) return 0 ;; | |
71 | esac | |
72 | ||
73 | return 1 | |
74 | } | |
75 | ||
76 | ||
77 | #:docstring provide: | |
78 | # Usage: provide symbol ... | |
79 | # | |
80 | # Register a list of symbols as provided features | |
81 | #:end docstring: | |
82 | ||
83 | ###;;;autoload | |
84 | function provide () | |
85 | { | |
86 | local feature | |
87 | ||
88 | for feature in "$@" ; do | |
89 | if ! featurep "${feature}" ; then | |
90 | FEATURES="${FEATURES} ${feature}" | |
91 | fi | |
92 | done | |
93 | ||
94 | return 0 | |
95 | } | |
96 | ||
97 | ||
98 | #:docstring require: | |
99 | # Usage: require feature {file} | |
100 | # | |
101 | # Load FEATURE if it is not already provided. Note that require does not | |
102 | # call `provide' to register features. The loaded file must do that | |
103 | # itself. If the package does not explicitly do a `provide' after being | |
104 | # loaded, require will complain about the feature not being provided on | |
105 | # stderr. | |
106 | # | |
107 | # Optional argument FILE means to try to load FEATURE from FILE. If no | |
108 | # file argument is given, require searches through FPATH (see fpath_search) | |
109 | # for the appropriate file. | |
110 | # | |
111 | # If the variable REQUIRE_FAILURE_FATAL is set, require will cause the | |
112 | # current shell invocation to exit, rather than merely return. This may be | |
113 | # useful for a shell script that vitally depends on a package. | |
114 | # | |
115 | #:end docstring: | |
116 | ||
117 | ###;;;autoload | |
118 | function require () | |
119 | { | |
120 | local feature="$1" | |
121 | local path="$2" | |
122 | local file | |
123 | ||
124 | if ! featurep "${feature}" ; then | |
125 | file=$(fpath_search "${feature}" "${path}") && source "${file}" | |
126 | ||
127 | if ! featurep "${feature}" ; then | |
128 | echo "require: ${feature}: feature was not provided." 1>&2 | |
129 | if [ "${REQUIRE_FAILURE_FATAL+set}" = "set" ]; then | |
130 | exit 1 | |
131 | fi | |
132 | return 1 | |
133 | fi | |
134 | fi | |
135 | ||
136 | return 0 | |
137 | } | |
138 | ||
139 | #:docstring fpath_search: | |
140 | # Usage: fpath_search filename {path ...} | |
141 | # | |
142 | # Search $FPATH for `filename' or, if `path' (a list) is specified, search | |
143 | # those directories instead of $FPATH. First the path is searched for an | |
144 | # occurrence of `filename.bash, then a second search is made for just | |
145 | # `filename'. | |
146 | #:end docstring: | |
147 | ||
148 | ###;;;autoload | |
149 | function fpath_search () | |
150 | { | |
151 | local name="$1" | |
152 | local path="$2" | |
153 | local suffix=".bash" | |
154 | local file | |
155 | ||
156 | if [ -z "${path}" ]; then path="${FPATH}"; fi | |
157 | ||
158 | for file in "${name}${suffix}" "${name}" ; do | |
159 | set -- $(IFS=':' | |
160 | set -- ${path} | |
161 | for p in "$@" ; do | |
162 | echo -n "${p:-.} " | |
163 | done) | |
164 | ||
165 | while [ $# -ne 0 ]; do | |
166 | test -f "${1}/${file}" && { file="${1}/${file}"; break 2 } | |
167 | shift | |
168 | done | |
169 | done | |
170 | ||
171 | if [ $# -eq 0 ]; then | |
172 | echo "fpath_search: ${name}: file not found in fpath" 1>&2 | |
173 | return 1 | |
174 | fi | |
175 | ||
176 | echo "${file}" | |
177 | return 0 | |
178 | } | |
179 | ||
180 | provide require | |
181 | ||
182 | # require.bash ends here |