summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRich Hickey <richhickey@gmail.com>2008-08-28 20:56:13 +0000
committerRich Hickey <richhickey@gmail.com>2008-08-28 20:56:13 +0000
commit29d7eb14abc1501f0d87df0f4bd9080a288513d4 (patch)
treedfa48cc5d9fe03c74216c7003bf53b13011baeb4
parent4b58241b944a261e708288bdaa520f549e0e259c (diff)
Added require/use/load-resources, contributed by Stephen C. Gilardi
enhanced ns macro to call these
-rw-r--r--src/clj/clojure/boot.clj274
1 files changed, 239 insertions, 35 deletions
diff --git a/src/clj/clojure/boot.clj b/src/clj/clojure/boot.clj
index 4a4d176a..6e3f878a 100644
--- a/src/clj/clojure/boot.clj
+++ b/src/clj/clojure/boot.clj
@@ -1388,9 +1388,6 @@
"Evaluates the form data structure (not text!) and returns the result."
[form] (. clojure.lang.Compiler (eval form)))
-;(defn defimports [& imports-maps]
-; (def *imports* (apply merge imports-maps)))
-
(defmacro doseq
"Repeatedly executes body (presumably for side-effects) with
binding-form bound to successive items from coll. Does not retain
@@ -1478,7 +1475,8 @@
"import-list => (package-symbol class-name-symbols*)
For each name in class-name-symbols, adds a mapping from name to the
- class named by package.name to the current namespace."
+ class named by package.name to the current namespace. Use :imports in the ns
+ macro in preference to calling this directly."
[& import-lists]
(when import-lists
(let [#^clojure.lang.Namespace ns *ns*
@@ -1488,13 +1486,6 @@
(. ns (importClass c (. Class (forName (str pkg "." c)))))) )
(apply import (rest import-lists))))
-(defmacro imports
- "import-list => (package-name class-names*)
-
- For each (unevaluated) name in class-names, adds a mapping from name to the
- class named by package-name.name to the current namespace."
- [& import-lists] `(import ~@(map #(list 'quote %) import-lists)))
-
(defn into-array
"Returns an array of the type of the first element in coll,
containing the contents of coll, which must be of a compatible
@@ -1986,7 +1977,7 @@
something else in the current namespace. Filters can be used to
select a subset, via inclusion or exclusion, or to provide a mapping
to a symbol different from the var's name, in order to prevent
- clashes."
+ clashes. Use :uses in the ns macro in preference to calling this directly."
[ns-sym & filters]
(let [ns (or (find-ns ns-sym) (throw (new Exception (str "No namespace: " ns-sym))))
fs (apply hash-map filters)
@@ -2001,23 +1992,6 @@
(throw (new java.lang.IllegalAccessError (str sym " is not public"))))
(. *ns* (refer (or (rename sym) sym) v)))))))
-(defmacro refers
- "refers to all public vars of ns, subject to filters.
- filters can include at most one each of:
-
- :exclude list-of-names
- :only list-of-names
- :rename map-of-fromname-toname
-
- For each public interned var in the namespace named by the symbol,
- adds a mapping from the name of the var to the var to the current
- namespace. Throws an exception if name is already mapped to
- something else in the current namespace. Filters can be used to
- select a subset, via inclusion or exclusion, or to provide a mapping
- to a symbol different from the var's name, in order to prevent
- clashes."
- [ns-name & filters] `(refer '~ns-name ~@(map #(list 'quote %) filters)))
-
(defn ns-refers
"Returns a map of the refer mappings for the namespace."
[#^clojure.lang.Namespace ns]
@@ -2978,12 +2952,26 @@
(defmacro ns
"Sets *ns* to the namespace named by name (unevaluated), creating it if needed.
- If the ns didn't already exist, refers the clojure namespace"
- [name]
- `(let [existed# (clojure.lang.Namespace/find '~name)]
- (in-ns '~name)
- (when-not existed#
- (clojure/refer '~'clojure))))
+ If the ns didn't already exist, refers the clojure namespace. references can be zero or more of:
+ (:requires ...) (:uses ...) (:imports ...) with the syntax of require/use/import respectively,
+ except the arguments are unevaluated and need not be quoted. Use of ns is preferred to
+ individual calls to in-ns/require/use/import:
+
+ (ns foo
+ (:requires (clojure.contrib sql sql.tests))
+ (:uses (my.lib this that))
+ (:imports (java.util Date Timer Random)
+ (java.sql Connection Statement)))"
+ [name & references]
+ (let [process-reference
+ (fn [[kname & args]]
+ `(~(symbol (subs (clojure/name kname) 0 (dec (count (clojure/name kname)))))
+ ~@(map #(list 'quote %) args)))]
+ `(let [existed# (clojure.lang.Namespace/find '~name)]
+ (in-ns '~name)
+ (when-not existed#
+ (clojure/refer '~'clojure))
+ ~@(map process-reference references))))
(defmacro defonce
"defs name to have the root value of the expr iff the named var has no root value,
@@ -2992,3 +2980,219 @@
`(let [v# (def ~name)]
(when-not (.hasRoot v#)
(def ~name ~expr))))
+
+;;;;;;;;;;; require/use/load-resources, contributed by Stephen C. Gilardi ;;;;;;;;;;;;;;;;;;
+
+(defonce
+ #^{:private true
+ :doc "A ref to a sorted set of symbols representing loaded libs"}
+ *loaded-libs* (ref (sorted-set)))
+
+(defonce
+ #^{:private true :doc
+ "True while a verbose load is pending"}
+ *loading-verbosely* false)
+
+(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- prependss
+ "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* #(reduce conj %1 %2)
+ (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)
+ {:keys [as reload reload-all require use 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 libspecs, prefix lists, and flags 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 (prependss arg opts))
+ (let [[prefix & args] arg]
+ (throw-if (nil? prefix) "prefix cannot be nil")
+ (doseq arg args
+ (apply load-lib prefix (prependss 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. Use :requires in the ns macro
+ in preference to calling this directly.
+
+ 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 :uses in the ns macro in preference to calling
+ this directly.
+
+ '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)))))