aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorscgilardi <scgilardi@gmail.com>2008-07-10 20:27:37 +0000
committerscgilardi <scgilardi@gmail.com>2008-07-10 20:27:37 +0000
commit7f120d2a96cd9fc3e34ce02d28d0d19f327d9b70 (patch)
treeda69bb8d7c9296283c75613c4d5debc78cb21bbb
parentde5bf569008fe1558d4e9fc06e0e6750b05b3384 (diff)
change evaluation of option args, revised docs, enhanced :verbose output
-rw-r--r--lib.clj387
1 files changed, 207 insertions, 180 deletions
diff --git a/lib.clj b/lib.clj
index 49e8825e..3e847c6f 100644
--- a/lib.clj
+++ b/lib.clj
@@ -1,118 +1,67 @@
-;; 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.
+;; 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.
;;
;; File: lib.clj
;;
-;; lib.clj provides facilities for loading and managing libs. A lib is
-;; is a unit of Clojure code contained in a file or other resource within
-;; classpath. The name of the lib's container is the lib's name followed
-;; by ".clj". A lib's name must be a valid Clojure symbol name. lib.clj
-;; also provides functions for finding and loading resources via class
-;; loaders visible to the Clojure runtime.
+;; lib.clj provides facilities for loading and managing libs. A lib is a
+;; unit of Clojure code contained in a file or other resource within
+;; classpath. Lib names must be valid Clojure symbol names. The name of a
+;; lib's container is the lib name followed by ".clj". lib.clj also
+;; provides general functions for finding and loading resources using the
+;; class loaders visible to the Clojure runtime.
;;
-;; Resources
-;;
-;; Function: find-resource
+;; Here is a brief overview of what's in lib.clj. For detailed docs please
+;; see the doc strings for the individual functions and macros.
;;
-;; Searches available class loaders for a resource given its path relative
-;; to classpath. Returns a URL if the resource is found or nil.
+;; Resources
;;
-;; Function: load-resource
+;; Function: find-resource
+;; Searches available class loaders for a resource, returns URL or nil
;;
-;; loads Clojure source from an absolute path expressed as a URI, URL
-;; or String
+;; Function: load-resource
+;; Loads Clojure source from an absolute path: URI, URL or String
;;
;; Core
;;
-;; Function: load-libs
-;;
-;; load-libs is the core function provided by lib.clj. Its arguments are
-;; libspecs and flags. It uses libspecs to find lib containers and flags
-;; to control how the libs are loaded. In an explicit call to load-libs
-;; libspecs must be quoted. This requirement is relaxed by the 'require'
-;; and 'use' macros described below.
-;;
-;; Libspecs
-;;
-;; A libspec is either a lib name or a list beginning with the lib name
-;; and followed by zero or more options.
-;;
-;; Options
-;;
-;; Options in a libspec are keywords followed by values. These are the
-;; options supported by load-libs and the kind of value each expects:
-;;
-;; :in a string specifying the path to the parent directory
-;; of the lib's container relative to one of the
-;; directories in classpath
-;; :ns a symbol specifying a namespace for the lib. If :ns
-;; is not present, its value defaults to the lib's name.
-;; See the :use flag below.
-;; :exclude a list as documented for clojure/refer
-;; :only a list as documented for clojure/refer
-;; :rename a map as documented for clojure/refer
-;;
-;; Flags
-;;
-;; Flags apply to all the libs specified in a single call to load-libs.
-;; These are the flags supported by load-libs:
-;;
-;; :require indicates that already loaded libs need not be reloaded
-;; :use triggers a call to clojure/refer for each lib's namespace
-;; after ensuring the lib is loaded. The call to refer
-;; includes any filter options in the libspec.
-;; :reload forces reloading of libs that are already loaded
-;; :reload-all implies :reload and also forces reloading of all libs
-;; on which each lib directly or indirectly depends
-;; :verbose triggers printing a message each time a lib is loaded
+;; Function: load-libs
+;; Loads lib(s) based libspecs and flags
;;
-;; (The :reload and :reload-all flags supersede :require.)
-;;
-;; Function: libs
-;;
-;; The libs function returns a sorted set of symbols naming the currently
-;; loaded libs.
+;; Function: libs
+;; Returns a sorted set of symbols naming the currently loaded libs
;;
;; Convenience
;;
-;; lib.clj provides two convenience macros to make calls to load-libs
-;; easier to write and read. Libspecs in calls to these macros do not
-;; need to be quoted.
-;;
-;; Macro: require
+;; Macro: require
+;; Loads libs. By default doesn't reload any that are already loaded
;;
-;; The require macro accepts libspecs and flags and ensures that the
-;; specified libs are loaded. By default, require will not reload a lib
-;; that is already loaded.
-;;
-;; Macro: use
-;;
-;; The use macro accepts libspecs and flags and first requires the libs
-;; and then refers to each lib's namespace using any filter options present
-;; in its libspec.
+;; Macro: use
+;; Loads libs like require and refers to each lib's namespace
;;
;; Examples
;;
-;; (load-libs :require 'sql '(test :in "private/resources"))
-;; (require sql (test :in "private/resources"))
+;; (load-libs :require 'sql '(test :in "private/resources"))
+;; (require sql (test :in "private/resources"))
+;;
+;; (load-libs :require :use 'sql 'ns-utils :verbose)
+;; (use sql ns-utils :verbose)
;;
-;; (load-libs :require :use 'sql 'ns-utils :verbose)
-;; (use sql ns-utils :verbose)
+;; (use :reload-all :verbose
+;; (sql :exclude '(get-connection)
+;; :rename '{execute-commands do-commands})
+;; ns-utils)
;;
-;; (use :reload-all :verbose
-;; (sql :exclude (get-connection) :rename {execute-commands do-commands})
-;; ns-utils)
-;; (use (sql))
+;; (use (sql))
;;
-;; (use (genclass :ns clojure))
+;; (load-libs :require :use '(genclass :ns 'clojure))
+;; (use (genclass :ns 'clojure))
;;
;; scgilardi (gmail)
-;; Created: 7 April 2008
+;; Created 7 April 2008
;;
;; Thanks to Stuart Sierra for providing many useful ideas, discussions
;; and code contributions for lib.clj.
@@ -142,15 +91,21 @@
(def
#^{:private true :doc
- "True while a verbose require is pending"}
+ "True while a verbose load is pending"}
*verbose*)
(init-once *verbose* false)
(def
#^{:private true :doc
- "A vector of the available class loaders ordered by the
- degree to which they are controlled by Clojure. The root
- loader's classpath can be extended with clojure/add-classpath"}
+ "A list of keywords that clojure/refer recognizes as filters"}
+ *filter-keys*)
+(init-once *filter-keys* '(:exclude :only :rename))
+
+(def
+ #^{:private true :doc
+ "A vector of the available class loaders ordered by the degree to which
+ they are controlled by Clojure. The root loader's classpath can be
+ extended with clojure/add-classpath"}
*class-loaders*
(let [root (.ROOT_CLASSLOADER RT)
runtime (.getClassLoader (identity RT))
@@ -159,90 +114,103 @@
[root system]
[root runtime system])))
+(defn- format
+ "Formats a string using String/format"
+ [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. See
java.util.Formatter for format string syntax."
[pred fmt & args]
- (if pred (throw (Exception. (String/format fmt (to-array args))))))
+ (when pred (throw (Exception. (apply format fmt args)))))
(def find-resource) ; forward declaration
(def load-resource) ; forward declaration
-(defn- load-lib
- "Loads a lib from <classpath>/in/ and ensures that namespace
- ns (if specified) exists"
- [sym in ns]
- (let [res (str sym ".clj")
- rel-path (if in (str in \/ res) res)
- url (find-resource rel-path)]
- (throw-if (not url) "resource '%s' not found in classpath" rel-path)
- (load-resource url)
- (throw-if (and ns (not (find-ns ns)))
- "namespace '%s' not found after loading resource '%s'"
- ns rel-path))
+(defn- load-one
+ "Loads one lib from a resoure and ensures that namespace ns (if
+ specified) exists"
+ [sym url ns]
+ (load-resource url)
+ (throw-if (and ns (not (find-ns ns)))
+ "namespace '%s' not found after loading '%s'" ns url)
(dosync
- (commute *libs* conj sym))
- (when *verbose*
- (println "loaded" sym)))
+ (commute *libs* conj sym)))
(defn- load-all
- "Loads a lib from <classpath>/in/ and forces a load of any
- libs on which it directly or indirectly depends"
- [sym in ns]
+ "Loads a lib from a resource and forces a load of any libs which it
+ directly or indirectly loads via require/use/load-libs"
+ [sym url ns]
(dosync
(commute *libs* set/union
(binding [*libs* (ref (sorted-set))]
- (load-lib sym in ns)
+ (load-one sym url ns)
@*libs*))))
-(defn- load-with-options
- "Load a lib with options expressed as sequential keywords and
- values"
+(defn- load-lib
+ "Loads a lib with options: sequential keywords and arguments. The
+ arguments to all options are evaluated so literal symbols or lists must
+ be quoted"
[sym & options]
- (let [opts (apply hash-map options)
- in (eval (:in opts))
- ns (eval (:ns opts))
+ (let [raw-opts (apply hash-map options)
+ opts (zipmap (keys raw-opts) (map eval (vals raw-opts)))
+ in (:in opts)
+ ns (:ns opts)
reload (:reload opts)
reload-all (:reload-all opts)
require (:require opts)
use (:use opts)
verbose (:verbose opts)
loaded (contains? @*libs* sym)
- namespace (and use (or ns sym))]
+ load (cond reload-all
+ load-all
+ (or reload (not require) (not loaded))
+ load-one)
+ namespace (when use (or ns sym))
+ res (str sym ".clj")
+ path (if in (str in \/ res) res)
+ url (find-resource path)
+ filter-opts (select-keys opts *filter-keys*)]
(binding [*verbose* (or *verbose* verbose)]
- (cond reload-all
- (load-all sym in namespace)
- (or reload (not require) (not loaded))
- (load-lib sym in namespace)))
- (when namespace
- (apply refer namespace options))))
-
-(defn- remove-quote
- "If form is a 'quote' special form, return the unquoted value
- else return form"
- [form]
- (if (and (seq? form)
- (= 'quote (first form))
- (= 2 (count form)))
- (second form) form))
+ (when load
+ (throw-if (not url) "'%s' not found in classpath" path)
+ (load sym url namespace)
+ (when *verbose*
+ (printf "(lib/load-resource \"%s\")\n" url)))
+ (when namespace
+ (apply refer namespace (mapcat seq filter-opts))
+ (when *verbose*
+ (printf "(clojure/refer '%s" namespace)
+ (dorun (map
+ #(printf " %s '%s" (key %) (print-str (val %)))
+ filter-opts))
+ (printf ")\n"))))))
;; Resources
(defn find-resource
- "Searches for a resource given a path relative to classpath via
+ "Searches for a resource given a path relative to classpath using
available ClassLoaders. Returns a URL if the resource is found or nil."
[rel-path]
(some #(.findResource % rel-path) *class-loaders*))
(defn load-resource
- "Loads Clojure source from a resource specified by an absolute path.
- The path may be a URI, URL, or String. Accepts any URI scheme
- supported by URLConnection (http and jar), plus file paths."
+ "Loads Clojure source from a resource specified by an absolute path. The
+ path may be a URI, URL, or String. Accepts any URI scheme supported by
+ URLConnection (http and jar), plus file paths."
[abs-path]
- (let [url (cond ; coerce argument into URL
- (instance? URL abs-path) abs-path
- (instance? URI abs-path) (.toURL abs-path)
- (string? abs-path) (URL. abs-path))]
+ (let [url (cond (instance? URL abs-path)
+ abs-path
+ (instance? URI abs-path)
+ (.toURL abs-path)
+ (string? abs-path)
+ (URL. abs-path))]
(throw-if (not url) "Cannot coerce %s to %s" (class abs-path) URL)
(with-open reader
(BufferedReader.
@@ -253,21 +221,54 @@
;; Core
(defn load-libs
- "Searches classpath for libs and loads them based on libspecs
- and flags. In addition to the flags documented for 'require'
- load-libs supports :require which indicates that already loaded
- libs need not be reloaded and :use which triggers a call to
- 'clojure/refer' for the lib's namespace after ensuring the lib
- is loaded. The :reload and :reload-all flags supersede
- :require."
+ "Searches classpath for libs and loads them. 'load-libs' accepts zero or
+ more arguments where each argument is either a libspec that identifies a
+ lib to load, or a flag that modifies how all the identified libs are
+ loaded.
+
+ A libspec is either a symbol or a list containing a symbol followed by
+ zero or more options. Since the arguments to 'load-libs' are evaluated
+ before the call, any literal libspecs passed in must be quoted.
+
+ The 'require' and 'use' macros offer a simpler interface to the
+ capabilities of load-libs and don't evaluate the libspecs they take as
+ arguments.
+
+ An option is a keyword followed by an argument.
+ Recognized options: :in, :ns, :exclude, :only, :rename
+
+ All arguments to options within libspecs are evaluated so literal lists
+ and symbols appearing within libspecs must be quoted.
+
+ The :in option's argument must evaluate to a string specifying the path
+ of the lib's parent directory relative to a location in classpath.
+
+ The :ns options's argument must evaluate to a symbol specifying the
+ namespace to refer for this lib if the :use flag is present. When the
+ :ns option is not present the namespace defaults to the one with the
+ same name as the lib.
+
+ The arguments and semantics for :exclude, :only, and :rename are those
+ documented for clojure/refer.
+
+ A flag is a keyword.
+ Recognized flags: :require, :use, :reload, :reload-all, :verbose
+
+ :require indicates that any identified libs that are already loaded need
+ not be reloaded
+ :use triggers referring to each lib's namespace after loading
+ :reload forces loading of all the identified libs even if they were
+ loaded previously. :reload supersedes :require
+ :reload-all implies :reload and also forces loading of all libs that the
+ identified libs directly or indirectly load via load-libs/require/use
+ :verbose triggers printing a message after loading each lib"
[& args]
(let [libspecs (filter (complement keyword?) args)
flags (filter keyword? args)
- options (interleave flags (repeat true))]
+ flag-opts (interleave flags (repeat true))]
(doseq libspec libspecs
- (let [libspec (remove-quote libspec)
- combine (if (symbol? libspec) cons concat)]
- (apply load-with-options (combine libspec options))))))
+ (let [combine (if (symbol? libspec) cons concat)]
+ (apply load-lib (combine libspec flag-opts))))))
(defn libs
"Returns a sorted set of symbols naming loaded libs"
@@ -277,32 +278,58 @@
;; Convenience
(defmacro require
- "Searches classpath for libs and loads them if they are not
- already loaded. Each argument is either a libspec or a flag.
- A libspec is a name or a seq of the form (name [options*]).
- Each option is a sequential keyword-value pair. 'require'
- supports the :in option whose value is the path of the lib's
- parent directory relative to a location in classpath.
-
- Flags may include:
-
- :reload
- :reload-all
- :verbose
-
- The :reload flag forces reloading of libs that are already
- loaded. The :reload-all flag implies :reload and also forces
- reloading of all libs on which the libs directly or indirectly
- depend. The :verbose flag triggers printing a message each time
- a lib is loaded."
+ "Searches classpath for libs and (by default) loads them if they are not
+ already loaded. 'require' accepts zero or more arguments where each
+ argument is either a libspec that identifies a lib to load, or a flag
+ that modifies how the identified libs are loaded.
+
+ A libspec is a symbol or a list containing a symbol followed by zero or
+ more options. 'require' does not evaluate its arguments so libspecs and
+ flags should not be quoted.
+
+ An option is a keyword followed by an argument.
+ Recognized options: :in
+
+ All arguments to options within libspecs are evaluated so literal lists
+ and symbols appearing within libspecs must be quoted.
+
+ The :in option's argument must evaluate to a string specifying the path
+ of the lib's parent directory relative to a location in classpath.
+
+ A flag is a keyword.
+ Recognized flags: :reload, :reload-all, :verbose
+
+ :reload forces loading of all the identified libs even if they were
+ loaded previously
+ :reload-all implies :reload and also forces loading of all libs that the
+ identified libs directly or indirectly load via load-libs/require/use.
+ :verbose triggers printing a message after loading each lib"
[& args]
`(apply load-libs :require '~args))
(defmacro use
- "Requires and refers to the named libs (see clojure/refer).
- Arguments are like those of 'lib/require', except that libspecs
- can contain an :ns option specifying a namespace to refer to
- (ns defaults to the lib's name) and additional options which
- are filters for clojure/refer."
+ "Searches classpath for libs and (by default) loads them if they are not
+ already loaded and refers to namespaces with the same name. 'use' accepts
+ zero or more arguments where each argument is either a libspec that
+ identifies a lib to load, or a flag that modifies how the identified libs
+ are loaded.
+
+ A libspec is a symbol or a list containing a symbol followed by zero or
+ more options. 'use' does not evaluate its arguments so libspecs and flags
+ should not be quoted.
+
+ 'use' recognizes all the libspecs, options and flags documented for
+ 'require' plus some additional options in libspecs.
+
+ Additional options: :ns, :exclude, :only, :rename
+
+ All arguments to options within libspecs are evaluated so literal lists
+ and symbols appearing within libspecs must be quoted.
+
+ The :ns options's argument must evaluate to a symbol specifying the
+ namespace to refer for this lib.
+
+ The arguments and semantics for :exclude, :only, and :rename are those
+ documented for clojure/refer."
[& args]
`(apply load-libs :require :use '~args))