diff options
author | scgilardi <scgilardi@gmail.com> | 2008-07-22 04:43:03 +0000 |
---|---|---|
committer | scgilardi <scgilardi@gmail.com> | 2008-07-22 04:43:03 +0000 |
commit | 1cd33b8b6888b5b54216569aa412c9e186e26dc4 (patch) | |
tree | 08cbe72318ec73a51531cbb79593c62969cca141 | |
parent | 939ee0cd19e1c2ec4b5a3b22839c550e9f46823b (diff) |
add directory for new lib.clj namespace
-rw-r--r-- | lib/lib.clj | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/lib/lib.clj b/lib/lib.clj new file mode 100644 index 00000000..3483b2e7 --- /dev/null +++ b/lib/lib.clj @@ -0,0 +1,338 @@ +;; 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 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. +;; +;; 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. +;; +;; Resources +;; +;; Function: find-resource +;; Searches available class loaders for a resource, returns URL or nil +;; +;; Function: load-resource +;; Loads Clojure source from an absolute path: URI, URL or String +;; +;; Core +;; +;; Function: load-libs +;; Loads lib(s) based libspecs and flags +;; +;; Function: libs +;; Returns a sorted set of symbols naming the currently loaded libs +;; +;; Convenience +;; +;; Macro: require +;; Loads libs. By default doesn't reload any that are already loaded +;; +;; Macro: use +;; Loads libs like require and refers to each lib's namespace +;; +;; Examples +;; +;; (load-libs :require 'sql '(sql-test :in "private/unit-tests")) +;; (require sql (sql-test :in "private/unit-tests")) +;; +;; (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 (sql)) +;; +;; (load-libs :require :use '(genclass :ns 'clojure)) +;; (use (genclass :ns 'clojure)) +;; +;; 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 'lib) +(clojure/refer 'clojure) + +(alias 'set 'clojure.set) + +(import '(clojure.lang RT)) +(import '(java.io BufferedReader InputStreamReader)) +(import '(java.net URI URL)) + +;; 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 set of symbols representing loaded libs"} + *libs*) +(init-once *libs* (ref (sorted-set))) + +(def + #^{:private true :doc + "True while a verbose load is pending"} + *verbose*) +(init-once *verbose* false) + +(def + #^{:private true :doc + "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)) + system (.getSystemClassLoader ClassLoader)] + (if (= system runtime) + [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] + (when pred (throw (Exception. (apply format fmt args))))) + +(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 + 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))) + +(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-libs" + [sym url ns] + (dosync + (commute *libs* set/union + (binding [*libs* (ref (sorted-set))] + (load-one sym url ns) + @*libs*)))) + +(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 [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) + load (cond reload-all + load-all + (or reload (not require) (not loaded)) + load-one) + namespace (when use (or ns sym)) + path (str (if in (str in \/)) sym ".clj") + url (find-resource path) + filter-opts (select-keys opts *filter-keys*)] + (binding [*verbose* (or *verbose* verbose)] + (when load + (when *verbose* + (printf "(lib/load-resource \"%s\")\n" url) + (flush)) + (throw-if (not url) "'%s' not found in classpath" path) + (load sym url namespace)) + (when namespace + (when *verbose* + (printf "(clojure/in-ns '%s)\n" (ns-name *ns*)) + (printf "(clojure/refer '%s" namespace) + (dorun (map + #(printf " %s '%s" (key %) (print-str (val %))) + filter-opts)) + (printf ")\n")) + (apply refer namespace (mapcat seq filter-opts)))))) + +;; 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." + [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." + [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. + (InputStreamReader. + (.openStream url))) + (.load Compiler reader (.getPath url) (.getFile url))))) + +;; Core + +(defn load-libs + "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) + flag-opts (interleave flags (repeat true))] + (doseq libspec libspecs + (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" + [] + @*libs*) + +;; Convenience + +(defmacro require + "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 + "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)) |