1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
|
;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and
;; distribution terms for this software are covered by the Common Public
;; License 1.0 (http://opensource.org/licenses/cpl.php) which can be found
;; in the file CPL.TXT at the root of this distribution. By using this
;; software in any fashion, you are agreeing to be bound by the terms of
;; this license. You must not remove this notice, or any other, from this
;; software.
;;
;; Libs
;;
;; A 'lib' is a named set of resources in classpath whose contents define
;; a library of Clojure code. Lib names are symbols and each lib is
;; associated with a Clojure namespace and a Java package that share its
;; name. A lib's name also locates its root directory within classpath
;; using Java's package name to classpath-relative path mapping. All
;; resources in a lib should be contained in the directory structure under
;; its root directory. All definitions a lib makes should be in its
;; associated namespace.
;;
;; Clojure loads a lib by loading its root resource. The root resource
;; path is derived from the root directory path by repeating its last
;; component and appending '.clj'. For example, the lib 'x.y.z has root
;; directory <classpath>/x/y/z; root resource <classpath>/x/y/z/z.clj.
;; The root resource should contain code to create the lib's namespace and
;; load any additional lib resources.
;;
;; Clojure provides functions to:
;;
;; - ensure libs are loaded while avoiding duplicate loads (require)
;; - require libs and refer to their namespaces (use)
;; - return a sorted set of symbols naming loaded libs (loaded-libs)
;; - load Clojure code from resources (load-resources)
;;
;; Libspecs
;;
;; A libspec identifies a lib to load. It's either a lib name or a vector
;; containing a lib name followed by options expressed as squential
;; keywords and arguments. Libspec examples:
;;
;; - 'clojure.contrib.sql
;; - '[clojure.contrib.sql :exclude (get-connection) :as sql]
;;
;; Prefix Lists
;;
;; It's common for Clojure code to depend on several libs whose names have
;; the same prefix. When specifying libs, prefix lists can be used to
;; reduce repetition. A prefix list contains the shared prefix followed by
;; libspecs with the shared prefix removed from the lib names. After
;; removing the prefix, the names that remain must not contain any
;; periods. For example, the following are equivalent:
;;
;; - (require 'clojure.contrib.def '[clojure.contrib.sql :as db])
;; - (require '(clojure.contrib def [sql :as db]))
;;
;; Examples
;;
;; (require '(clojure.contrib sql sql.tests))
;; (use '(clojure.contrib sql ns-utils) :verbose)
;;
;; (use :reload-all :verbose
;; '(clojure.contrib
;; [sql :exclude (get-connection)
;; :rename {execute-commands do-commands}]
;; [ns-utils :as nsu]))
;;
;; scgilardi (gmail)
;; Created 7 April 2008
;;
;; Thanks to Stuart Sierra for providing many useful ideas, discussions
;; and code contributions for lib.clj.
(clojure/in-ns 'clojure)
;; Private
(defmacro init-once
"Initializes a var exactly once. The var must already exist."
{:private true}
[var init]
`(let [v# (resolve '~var)]
(when-not (.isBound v#)
(.bindRoot v# ~init))))
(def
#^{:private true :doc
"A ref to a sorted set of symbols representing loaded libs"}
*loaded-libs*)
(init-once *loaded-libs* (ref (sorted-set)))
(def
#^{:private true :doc
"True while a verbose load is pending"}
*loading-verbosely*)
(init-once *loading-verbosely* false)
(defn- format
"Formats a string using String/format, see java.util.Formatter for format
string syntax"
[fmt & args]
(String/format fmt (to-array args)))
(defn- printf
"Prints formatted output"
[fmt & args]
(print (apply format fmt args)))
(defn- throw-if
"Throws an exception with a message if pred is true"
[pred fmt & args]
(when pred
(let [message (apply format fmt args)
exception (Exception. message)
raw-trace (.getStackTrace exception)
boring? #(not= (.getMethodName %) "doInvoke")
trace (into-array (drop 2 (drop-while boring? raw-trace)))]
(.setStackTrace exception trace)
(throw exception))))
(defn- libspec?
"Returns true if x is a libspec"
[x]
(or (symbol? x)
(and (vector? x)
(or
(nil? (second x))
(keyword? (second x))))))
(defn- prepend
"Prepends a symbol or a seq to coll"
[x coll]
(if (symbol? x)
(cons x coll)
(concat x coll)))
(defn- root-directory
"Returns the root directory path for a lib"
[lib]
(str \/
(.. (name lib)
(replace \- \_)
(replace \. \/))))
(defn- root-resource
"Returns the root resource path for a lib"
[lib]
(let [d (root-directory lib)
i (inc (.lastIndexOf d (int \/)))
leaf (.substring d i)]
(str d \/ leaf ".clj")))
(def load-resources)
(defn- load-one
"Loads a lib given its name. If need-ns, ensures that the associated
namespace exists after loading. If require, records the load so any
duplicate loads can be skipped."
[lib need-ns require]
(load-resources (root-resource lib))
(throw-if (and need-ns (not (find-ns lib)))
"namespace '%s' not found after loading '%s'"
lib (root-resource lib))
(when require
(dosync
(commute *loaded-libs* conj lib))))
(defn- load-all
"Loads a lib given its name and forces a load of any libs it directly or
indirectly loads. If need-ns, ensures that the associated namespace
exists after loading. If require, records the load so any duplicate loads
can be skipped."
[lib need-ns require]
(dosync
(commute *loaded-libs* clojure.set/union
(binding [*loaded-libs* (ref (sorted-set))]
(load-one lib need-ns require)
@*loaded-libs*))))
(defn- load-lib
"Loads a lib with options"
[prefix lib & options]
(throw-if (and prefix (pos? (.indexOf (name lib) (int \.))))
"lib names inside prefix lists must not contain periods")
(let [lib (if prefix (symbol (str prefix \. lib)) lib)
opts (apply hash-map options)
as (:as opts)
reload (:reload opts)
reload-all (:reload-all opts)
require (:require opts)
use (:use opts)
verbose (:verbose opts)
loaded (contains? @*loaded-libs* lib)
load (cond reload-all
load-all
(or reload (not require) (not loaded))
load-one)
need-ns (or as use)
filter-opts (select-keys opts '(:exclude :only :rename))]
(binding [*loading-verbosely* (or *loading-verbosely* verbose)]
(if load
(load lib need-ns require)
(throw-if (and need-ns (not (find-ns lib)))
"namespace '%s' not found" lib))
(when (and need-ns *loading-verbosely*)
(printf "(clojure/in-ns '%s)\n" (ns-name *ns*)))
(when as
(when *loading-verbosely*
(printf "(clojure/alias '%s '%s)\n" as lib))
(alias as lib))
(when use
(when *loading-verbosely*
(printf "(clojure/refer '%s" lib)
(doseq opt filter-opts
(printf " %s '%s" (key opt) (print-str (val opt))))
(printf ")\n"))
(apply refer lib (mapcat seq filter-opts))))))
(defn- load-libs
"Loads libs, interpreting prefix lists and libspecs for forwarding to
load-lib"
[& args]
(let [flags (filter keyword? args)
opts (interleave flags (repeat true))
args (filter (complement keyword?) args)]
(doseq arg args
(if (libspec? arg)
(apply load-lib nil (prepend arg opts))
(let [[prefix & args] arg]
(throw-if (nil? prefix) "prefix cannot be nil")
(doseq arg args
(apply load-lib prefix (prepend arg opts))))))))
;; Public
(defn require
"Loads libs, skipping any that are already loaded. Each argument is
either a libspec that identifies a lib, a prefix list that identifies
multiple libs whose names share a common prefix, or a flag that modifies
how all the identified libs are loaded.
Libs
A 'lib' is a named set of resources in classpath whose contents define a
library of Clojure code. Lib names are symbols and each lib is associated
with a Clojure namespace and a Java package that share its name. A lib's
name also locates its root directory within classpath using Java's
package name to classpath-relative path mapping. All resources in a lib
should be contained in the directory structure under its root directory.
All definitions a lib makes should be in its associated namespace.
'require loads a lib by loading its root resource. The root resource path
is derived from the root directory path by repeating its last component
and appending '.clj'. For example, the lib 'x.y.z has root directory
<classpath>/x/y/z; root resource <classpath>/x/y/z/z.clj. The root
resource should contain code to create the lib's namespace and load any
additional lib resources.
Libspecs
A libspec is a lib name or a vector containing a lib name followed by
options expressed as sequential keywords and arguments.
Recognized options: :as
:as takes a symbol as its argument and makes that symbol an alias to the
lib's namespace in the current namespace.
Prefix Lists
It's common for Clojure code to depend on several libs whose names have
the same prefix. When specifying libs, prefix lists can be used to reduce
repetition. A prefix list contains the shared prefix followed by libspecs
with the shared prefix removed from the lib names. After removing the
prefix, the names that remain must not contain any periods.
Flags
A flag is a keyword.
Recognized flags: :reload, :reload-all, :verbose
:reload forces loading of all the identified libs even if they are
already loaded
:reload-all implies :reload and also forces loading of all libs that the
identified libs directly or indirectly load via require or use
:verbose triggers printing information about calls to load-resources,
alias, and refer"
[& args]
(apply load-libs :require args))
(defn use
"Like 'require, but also refers to each lib's namespace using
clojure/refer.
'use accepts additional options in libspecs: :exclude, :only, :rename.
The arguments and semantics for :exclude, :only, and :rename are the same
as those documented for clojure/refer."
[& args]
(apply load-libs :require :use args))
(defn loaded-libs
"Returns a sorted set of symbols naming the currently loaded libs"
[]
@*loaded-libs*)
(defn load-resources
"Loads Clojure code from resources in classpath. A path is interpreted as
classpath-relative if it begins with a slash or relative to the root
directory for the current namespace otherwise."
[& paths]
(doseq path paths
(let [path (if (.startsWith path "/")
path
(str (root-directory (ns-name *ns*)) \/ path))]
(when *loading-verbosely*
(printf "(clojure/load-resources \"%s\")\n" path)
(flush))
(.loadResourceScript clojure.lang.RT (.substring path 1)))))
|