diff options
author | scgilardi <scgilardi@gmail.com> | 2008-08-16 01:17:36 +0000 |
---|---|---|
committer | scgilardi <scgilardi@gmail.com> | 2008-08-16 01:17:36 +0000 |
commit | 0f0d41f607fd859daff9964429097ff9a0c78b08 (patch) | |
tree | 900fa42eccc721ede659e7ae30dfbf38f3200945 | |
parent | 988c9cc7993be51ddfd3a9c230ae8d6b37eaf221 (diff) |
lib.clj: overhauled comments and docs, made prefix lists nest, added init-ns
-rw-r--r-- | lib/lib.clj | 391 |
1 files changed, 208 insertions, 183 deletions
diff --git a/lib/lib.clj b/lib/lib.clj index 98bd8de8..2287a7ca 100644 --- a/lib/lib.clj +++ b/lib/lib.clj @@ -6,70 +6,87 @@ ;; this license. You must not remove this notice, or any other, from this ;; software. ;; -;; lib -;; -;; lib provides functions for loading and managing Clojure source code -;; contained in Java resources. lib tracks what it loads at the -;; granularity of namespaces and provides convenient functions for -;; ensuring that namespace definitions have been loaded while avoiding -;; duplicate loads. -;; -;; Namespace names are symbols. Every namespace has an associated -;; 'namespace directory' whose classpath-relative path is derived from the -;; namespace name by replacing any periods with slashes. Clojure -;; namespaces map one-to-one with Java packages. -;; -;; A 'lib' is a unit of Clojure code contained in a resource within -;; classpath. A lib name is a symbol whose name is a concatenation of a -;; namespace name, a period, and a namespace-relative name. A lib is -;; contained in a resource whose classpath-relative path is derived from -;; the lib name by replacing all periods with slashes and then appending -;; ".clj". -;; -;; To load a namespace definition, lib loads the 'root lib' for the -;; namespace. The root lib's name is derived from the namespace name by -;; repeating its "leaf" portion. For example, the root lib for the -;; namespace 'clojure.contrib.def is the lib 'clojure.contrib.def.def -;; contained in the resource <classpath>/clojure/contrib/def/def.clj . -;; -;; The following code ensures that 'clojure.contrib.def and -;; 'clojure.contrib.sql are loaded: +;; clojure.contrib.lib (Lib) ;; -;; (require '(clojure.contrib def sql)) +;; Lib provides functions for loading and managing Clojure source code +;; contained in Java resources. ;; -;; In cases where a namespace is defined by more than one lib, all the -;; libs should be contained within the hierarchy of directories under the -;; namespace directory. The root lib must (drectly or indirectly) load the -;; additional libs. For example, a hypothetical namespace named -;; 'clojure.contrib.math-funcs might be defined in multiple libs contained -;; in resources like these: +;; A 'lib' is a unit of Clojure source code contained in a Java resource +;; and named by a symbol. The lib's name is used to identify the lib and +;; to locate the resource that contains it. ;; -;; <classpath>/clojure/contrib/math_funcs/math_funcs.clj -;; <classpath>/clojure/contrib/math_funcs/trig.clj -;; <classpath>/clojure/contrib/math_funcs/logarithms.clj -;; <classpath>/clojure/contrib/math_funcs/bessel.clj -;; <classpath>/clojure/contrib/math_funcs/inverses/trig.clj -;; <classpath>/clojure/contrib/math_funcs/inverses/logarithms.clj +;; Lib provides functions to: ;; -;; The following code ensures that 'clojure.contrib.math-funcs is loaded: +;; - find a resource given a classpath-relative path +;; - load code from a resource given an absolute path +;; - load libs from resources given lib names +;; - load namespace definitions given namespace specs +;; - ensure namespace definitions have been loaded while avoiding +;; duplicate loads, and +;; - create a namespace and refer 'clojure into it succinctly ;; -;; (require '(clojure.contrib math-funcs)) +;; Symbols, Namespaces, Packages ;; -;; This is the portion of math_funcs.clj that loads the additional libs: +;; Symbols in Clojure are two-part identifiers - with an optional +;; namespace and a name, both strings. Namespaces are used to distinguish +;; two symbols that have the same name. Vars are named by symbols that +;; must have a namespace part, and thus can be considered to be in +;; namespaces. [From Clojure documentation] ;; -;; (clojure/in-ns 'clojure.contrib.math-funcs) +;; Packages in Java play a role similar to Clojure namespaces - they +;; partition the global namespace to allow large programs to avoid name +;; conflicts. Java defines a mapping from package names to directories +;; within classpath: components of a package name separated by periods +;; correspond to components of a classpath-relative path separated by +;; slashes. Lib uses this same mapping to locate libs and namespaces in +;; classpath. ;; -;; (clojure.contrib.lib/load-libs -;; '(clojure.contrib.math-funcs -;; trig logarithms bessel inverses.trig inverses.logarithms)) +;; Loading Libs ;; -;; lib also provides general functions for finding and loading resources -;; using the class loaders visible to the Clojure runtime. +;; A lib's name provides the location of the resource that contains it in +;; classpath. The resource name is the last component of the lib's path +;; followed by ".clj". For example, a lib named 'a.b.c is contained in the +;; resource "<classpath>/a/b/c.clj". ;; -;; Here is a brief overview of what's in lib. For detailed docs please see -;; the doc strings for the individual functions. +;; Loading Namespaces ;; -;; Resources +;; To load a namespace definition, Lib loads the 'root lib' for the +;; namespace. The root lib's name is derived from the namespace name by +;; repeating its last component. For example, the root lib for the +;; namespace 'x.y.z is the lib 'x.y.z.z contained in the resource +;; "<classpath>/x/y/z/z.clj". The namespace definition may be entirely +;; within the root lib or it may span multiple libs in the hierarchy of +;; directories at and under the namespace's directory. In the latter case +;; the root lib must include commands to (directly or indirectly) load the +;; remaining libs. +;; +;; Nsspecs +;; +;; An nsspec specifies a namespace to load. It is either a namespace name +;; or a list containing the namespace name and zero or more options +;; expressed as squential keywords and values. Nsspec examples: +;; - 'clojure.contrib.sql +;; - '(clojure.contrib.sql) +;; - '(clojure.contrib.sql :exclude (get-connection)). +;; - '(clojure.contrib.sql :as sql) +;; +;; Prefix Lists +;; +;; It is common for Clojure code to depend on several libs whose names +;; have one or more initial components in common. When specifying lib or +;; namespace names for Lib to use, prefix lists can be used to reduce +;; repetition in the call. A prefix list is a list containing the shared +;; prefix followed by lib or namespace names and/or prefix lists with the +;; shared prefix elided. For example, the following are all equivalent: +;; (load-libs 'a.b.c 'a.b.d 'a.c.e 'a.c.f) ; all names fully qualified +;; (load-libs '(a.b c d) '(a.c e f)) ; 'a.b and 'a.c extracted +;; (load-libs '(a (b c d) (c e f))) ; all common prefixes extracted +;; Symbols containing explicit periods and the equivalent prefix lists may +;; be mixed freely. +;; +;; Lib Functions +;; +;; Resources ;; ;; Function: find-resource ;; Searches available class loaders for a resource, returns URL or nil @@ -77,27 +94,27 @@ ;; Function: load-resource ;; Loads Clojure source from an absolute path: URI, URL or String ;; -;; Raw Libs +;; Libs ;; ;; Function: load-libs -;; Loads libs by name from arbitrary locations under classpath +;; Loads libs by name from arbitrary locations within classpath ;; -;; Namespaces +;; Namespaces ;; ;; Function: load-namespaces ;; Loads namespace definitions by loading namespace root libs ;; ;; Function: namespaces -;; Returns a sorted set of symbols naming the set of namespaces -;; that lib has loaded and is tracking +;; Returns a sorted set symbols naming namespaces that have been loaded +;; with the :require option - used to track and avoid duplicate loads. ;; ;; Function: require -;; Loads namespace definitions that are not already loaded +;; Loads namespace definitions that have not yet been loaded ;; ;; Function: use ;; Requires namespaces and refers to them using clojure/refer ;; -;; Examples +;; Examples ;; ;; (load-namespaces :require '(clojure.contrib sql sql.tests)) ;; (require '(clojure.contrib sql sql.tests)) @@ -190,14 +207,29 @@ (.setStackTrace exception trace) (throw exception)))) +(defn- nsspec? + "Returns true if x is an nsspec" + [x] + (or (symbol? x) + (nil? (second x)) + (keyword? (second x)))) + +(defn- prepend + "Prepends a symbol or collection to coll" + [x coll] + (if (symbol? x) + (cons x coll) + (concat x coll))) + (def find-resource) ; forward declaration (def load-resource) ; forward declaration (defn- load-one - "Loads one lib from a resoure and ensures that namespace ns (if - not nil) exists. If require is true, also records the load so - a duplicate load will be skipped." - [sym url require need-ns] + "Loads one lib from a resource at url. If need-ns is 'true' ensures that + the namespace named by sym exists after loading. If require is 'true' + also records the namespace named by sym as loaded so any duplicate loads + can be skipped." + [url need-ns sym require] (load-resource url) (throw-if (and need-ns (not (find-ns sym))) "namespace '%s' not found after loading '%s'" sym url) @@ -206,17 +238,17 @@ (commute *namespaces* conj sym)))) (defn- load-all - "Loads a lib from a resource and forces a load of any libs which it - directly or indirectly loads via require/use/load-namespaces" - [sym url require need-ns] + "Loads a lib from a resource at url and forces a load of any namespaces + it directly or indirectly loads via require/use/load-namespaces" + [url need-ns sym require] (dosync (commute *namespaces* set/union (binding [*namespaces* (ref (sorted-set))] - (load-one sym url require need-ns) + (load-one url need-ns sym require) @*namespaces*)))) (defn- name-path - "Returns a classpath-relative path given the name of a symbol" + "Returns a classpath-relative path given a symbol name" [name] (.. name (replace \- \_) @@ -241,19 +273,19 @@ (let [sym (symbol (str prefix (when prefix \.) sym)) opts (apply hash-map options) as (:as opts) - raw (:raw opts) reload (:reload opts) reload-all (:reload-all opts) require (:require opts) + root (:root opts) use (:use opts) verbose (:verbose opts) loaded (contains? @*namespaces* sym) load (cond reload-all load-all - (or raw reload (not require) (not loaded)) + (or reload (not require) (not loaded)) load-one) need-ns (or as use) - path ((if raw lib-path root-lib-path) sym) + path ((if root root-lib-path lib-path) sym) url (find-resource path) filter-opts (select-keys opts *filter-keys*)] (binding [*verbose* (or *verbose* verbose)] @@ -262,25 +294,42 @@ (when *verbose* (printf "(clojure.contrib.lib/load-resource \"%s\")\n" url) (flush)) - (load sym url require need-ns)) + (load url need-ns sym require)) (throw-if (and need-ns (not (find-ns sym))) "namespace '%s' not found" sym) + (when (and need-ns *verbose*) + (printf "(clojure/in-ns '%s)\n" (ns-name *ns*))) (when as + (when *verbose* + (printf "(clojure/alias '%s '%s)\n" as sym)) (alias as sym)) (when use (when *verbose* - (printf "(clojure/in-ns '%s)\n" (ns-name *ns*)) (printf "(clojure/refer '%s" sym) (doseq opt filter-opts (printf " %s '%s" (key opt) (print-str (val opt)))) (printf ")\n")) (apply refer sym (mapcat seq filter-opts)))))) +(defn- load-prefix-list + "Loads libs and handles (nested) prefix lists" + [load? prefix opts & args] + (doseq arg args + (if (load? arg) + (apply load-lib prefix (prepend arg opts)) + (let [[nested-prefix & nested-args] arg] + (throw-if (nil? nested-prefix) "prefix cannot be nil") + (apply load-prefix-list + load? + (symbol (str prefix (when prefix \.) nested-prefix)) + opts + nested-args))))) + ;; Resources (defn find-resource - "Searches for a resource given a path relative to classpath using - available ClassLoaders. Returns a URL if the resource is found or nil." + "Searches for a resource given a classpath-relative path using available + ClassLoaders. If the resource is found, returns its URL, otherwise nil." [rel-path] (some #(.findResource % rel-path) *class-loaders*)) @@ -302,91 +351,99 @@ (.openStream url))) (.load Compiler reader (.getPath url) (.getFile url))))) -;; Raw Libs +;; Libs (defn load-libs - "Searches classpath for libs and loads them. Each argument is either a - libgroupspec that identifies a group of libs to load or a flag that - modifies how all the identified libs are loaded. Symbols identify paths - within classpath. Symbol names map to paths by replacing periods with - slashes. - - A libgroupspec is a list containing a symbol that identifies a prefix - directory followed by libspecs that identify libs relative to that - directory. + "Loads libs - Clojure source contained in resources in classpath. Each + argument is a either a symbol that identifies a lib, a prefix list that + identifies multiple libs with names that share common prefixes, or a flag + that modifies how all the identified libs are loaded. Symbol names map to + paths within classpath: components of the symbol name separated by + periods correspond to components in classpath-relative paths separated by + slashes. The full resource path is the mapped path with '.clj' + appended. For example, the resource containing the lib 'a.b.c is + '<classpath>/a/b/c.clj'. + + + Multiple libs whose names share the same period-delimited prefix can be + identified succinctly using a prefix list: a list containing the shared + prefix followed by symbols and/or prefix lists with the shared prefix + elided. For example, the following are all equivalent: + (load-libs 'a.b.c 'a.b.d 'a.c.e 'a.c.f) ; all names fully qualified + (load-libs '(a.b c d) '(a.c e f)) ; 'a.b and 'a.c extracted + (load-libs '(a (b c d) (c e f))) ; all common prefixes extracted + Symbols containing explicit periods and the equivalent prefix lists may + be mixed freely. - A libspec is a symbol. - - A flag is a keyword. Recognized flags: :reload-all, :verbose :reload-all forces loading of all namespaces that the identified libs directly or indirectly load via load-namespaces/require/use :verbose triggers printing information about each load and refer" [& args] - (let [libargs (filter (complement keyword?) args) - flags (filter keyword? args) - flag-opts (interleave flags (repeat true))] - (doseq libarg libargs - (if (symbol? libarg) - (apply load-lib nil libarg :raw true flag-opts) - (let [[prefix & libspecs] libarg] - (throw-if (not prefix) "prefix cannot be nil") - (doseq libspec libspecs - (apply load-lib prefix libspec :raw true flag-opts))))))) + (let [flags (filter keyword? args) + opts (interleave flags (repeat true)) + args (filter (complement keyword?) args)] + (apply load-prefix-list symbol? nil opts args))) ;; Namespaces (defn load-namespaces - "Searches classpath for namespace definitions and loads them. Each - argument is either an nsgroupspec that identifies a group of namespaces - to load or a flag that modifies how all the identified namespaces are - loaded. Symbols identify paths within classpath. Symbol names map to - paths by replacing periods with slashes. - - An nsgroupspec is a list containing a symbol that identifies a prefix - directory followed by nsspecs that identify namespace directories - relative to that prefix and loading options. - - An nsspec is a symbol or a list containing a symbol and options. The - recognized options are only effective when the :use flag is set. + "Loads namespace definitions contained in resources in classpath. + Each argument is a either an nsspec that identifies a namespace, a prefix + list that identifies multiple namespaces with names that share common + prefixes, or a flag that modifies how all the identified namespaces are + loaded. Namespace names map to paths within classpath: components of the + namespace name separated by periods correspond to components in + classpath-relative paths separated by slashes. To load the namespace + definition, load-namespaces loads the namespace's 'root lib'. The root + lib's name is derived from the namespace name by repeating its last + component. The root lib for namespace 'a.b.c is the lib 'a.b.c.c + contained in the resource '<classpath>/a/b/c/c.clj'. The namespace + definition need not be completely contained in its root lib. If it's not + the root lib must contain code to (directly or indirectly) load the + additional libs using load-libs. + + An nsspec is a symbol or a list containing a symbol and options. Some + options may have meaning only when specific flags are set. An option is a keyword followed by an argument. - Recognized options: :exclude, :only, :rename + Recognized options: :as :exclude, :only, :rename + The argument for :as is a symbol to use as an alias for the specified + namespace. The arguments and semantics for :exclude, :only, and :rename are those - documented for clojure/refer. + documented for clojure/refer. They are only effective when the :use flag + is present. + + Multiple namespaces whose names share the same period-delimited prefix + can be identified succinctly using a prefix list: a list containing the + shared prefix followed by nsspecs and/or prefix lists with the shared + prefix elided. For example, the following are all equivalent: + (load-namespaces 'a.b.c 'a.b.d 'a.c.e 'a.c.f) ; fully qualified names + (load-namespaces '(a.b c d) '(a.c e f)) ; 'a.b and 'a.c extracted + (load-namespaces '(a (b c d) (c e f))) ; all common prefixes extracted + Symbols containing explicit periods and the equivalent prefix lists may be + mixed freely. A flag is a keyword. Recognized flags: :require, :use, :reload, :reload-all, :verbose :require indicates that any identified namespace definitions that are already loaded need not be reloaded - :use triggers referring to each namespace with its options as filters + :use triggers referring to each namespace with optional filters specified + in its options :reload forces loading of all the identified namespace definitions even if they are already loaded. :reload supersedes :require :reload-all implies :reload and also forces loading of all namespace definitions that the identified namespace definitions directly or indirectly load via load-namespaces/require/use - :verbose triggers printing information about each load and refer" + :verbose triggers printing information about each load, alias and refer" [& args] - (let [nsargs (filter (complement keyword?) args) - flags (filter keyword? args) - flag-opts (interleave flags (repeat true))] - (doseq nsarg nsargs - (cond (symbol? nsarg) - (apply load-lib nil nsarg flag-opts) - (nil? (second nsarg)) - (apply load-lib nil (first nsarg) flag-opts) - (keyword? (second nsarg)) - (apply load-lib nil (concat nsarg flag-opts)) - :else - (let [[prefix & nsspecs] nsarg] - (throw-if (not prefix) "prefix cannot be nil") - (doseq nsspec nsspecs - (if (symbol? nsspec) - (apply load-lib prefix nsspec flag-opts) - (apply load-lib prefix (concat nsspec flag-opts))))))))) + (let [flags (cons :root (filter keyword? args)) + opts (interleave flags (repeat true)) + args (filter (complement keyword?) args)] + (apply load-prefix-list nsspec? nil opts args))) (defn namespaces "Returns a sorted set of symbols naming loaded namespaces" @@ -394,60 +451,28 @@ @*namespaces*) (defn require - "Searches classpath for namespace definitions and loads them if they are - not already loaded. Each argument is either an nsgroupspec that - identifies a group of namespaces to load or a flag that modifies how all - the identified namespaces are loaded. Symbols identify paths within - classpath. Symbol names map to paths by replacing periods with slashes. - - An nsgroupspec is a list containing a symbol that identifies a prefix - directory followed by nsspecs that identify namespace directories - relative to that prefix and loading options. - - In the general case, an nsspec is a symbol or a list containing a symbol - and options. In the case of require, no options are available so an - nsspec should be a symbol. (see also load-namespaces and use) - - A flag is a keyword. - Recognized flags: :reload, :reload-all, :verbose - - :reload forces loading of all the identified namespace definitions even - if they are already loaded. - :reload-all implies :reload and also forces loading of all namespace - definitions that the identified namespace definitions directly or - indirectly load via load-namespaces/require/use - :verbose triggers printing information about each load and refer" + "Ensures that namespaces are loaded while avoiding duplicate + loads. Arguments are described in the docs for 'load-namespaces. The + :require flag is automatically set." [& args] (apply load-namespaces :require args)) (defn use - "Searches classpath for namespace definitions, loads them if they are not - already loaded, and refers to them. Each argument is either an - nsgroupspec that identifies a group of namespaces to load or a flag that - modifies how all the identified namespaces are loaded. Symbols identify - paths within classpath. Symbol names map to paths by replacing periods - with slashes. - - An nsgroupspec is a list containing a symbol that identifies a prefix - directory followed by nsspecs that identify namespace directories - relative to that prefix and loading options. - - An nsspec is a symbol or a list containing a symbol and options. - - An option is a keyword followed by an argument. - Recognized options: :exclude, :only, :rename - - The arguments and semantics for :exclude, :only, and :rename are those - documented for clojure/refer. - - A flag is a keyword. - Recognized flags: :reload, :reload-all, :verbose - - :reload forces loading of all the identified namespace definitions even - if they are already loaded. :reload supersedes :require - :reload-all implies :reload and also forces loading of all namespace - definitions that the identified namespace definitions directly or - indirectly load via load-namespaces/require/use - :verbose triggers printing information about each load and refer" + "Ensures that namespaces are loaded while avoiding duplicate loads and + refers to them in the current namespace. Arguments are described in the + docs for 'load-namespaces. The :require and :use flags are automatically + set." [& args] (apply load-namespaces :require :use args)) + +(defn init-ns + "Creates ns (if necessary), makes it current, and uses clojure/refer to + make the 'clojure namespace available to it. Optional clojure-filters can + be passed along to clojure/refer to modify how much of and how 'clojure + is pulled in. init-ns also refers to (all of) 'clojure.contrib.lib. + Namespace root libs will typically begin with a call to init-ns; any + subordinate libs can begin with a call to in-ns." + [ns & clojure-filters] + (in-ns ns) + (apply refer 'clojure clojure-filters) + (refer 'clojure.contrib.lib)) |