diff options
author | scgilardi <scgilardi@gmail.com> | 2008-04-08 18:14:35 +0000 |
---|---|---|
committer | scgilardi <scgilardi@gmail.com> | 2008-04-08 18:14:35 +0000 |
commit | ef16c987339345d375f9bd6e2584d9e0fbe713ea (patch) | |
tree | 9ede054a36110bd0683a23a41c2363ee99e77d43 | |
parent | b717b02aca68c3b3c64cc7fe9aa6fc65496b4299 (diff) |
jewel.clj: overhaul with suggestions from Stuart Sierra
-rw-r--r-- | jewel.clj | 188 |
1 files changed, 114 insertions, 74 deletions
@@ -10,38 +10,36 @@ ;; ;; A 'jewel' is a Clojure source file that follows these conventions: ;; -;; - has a basename that is a valid symbol name in Clojure, -;; - has the extension ".clj", and -;; - defines a namespace that has the same name as the basename. +;; - has a basename that is a valid symbol name in Clojure +;; - has the extension ".clj" ;; ;; A jewel will typically also contain forms that provide something useful -;; for a Clojure program to use. +;; for a Clojure program to use. It may also define a namespace with the +;; same name as its basename. ;; ;; This file is an example of a jewel. It follows the naming conventions -;; and provides two functions that make it convenient to use jewels from -;; Clojure source code and the Clojure repl: +;; and provides these functions in the 'jewel' namespace that make it +;; convenient to use jewels from Clojure source code and the Clojure repl: ;; -;; 'jewel/require' takes a symbol that names a jewel and zero or more -;; optional keyword parameters. -;; jewel/require: -;; - ensures that the jewel is either already loaded or loads it from -;; within classpath. Optionally, the caller may specify a path to -;; the jewel's parent directory relative to a location in classpath; -;; - refers to the jewel's namespace. Optionally, the caller may -;; specify any filters documented for clojure/refer as options to -;; jewel/require; -;; - optionally forces a (re)load of either just the jewel itself or of -;; the jewel and all of the jewels on which it (directly or indirectly) -;; depends; -;; - optionally prints a message each time it loads a jewel. +;; 'require' searches classpath for jewels and loads them if +;; they are not already loaded ;; -;; 'jewel/jewels' returns a sorted list of loaded jewels. +;; 'use' requires jewels and refers to their namespaces +;; +;; 'jewels' returns a sorted list of loaded jewels +;; +;; 'load-uri' loads Clojure source from a location +;; +;; 'load-system-resource' loads Clojure source from a resource in +;; classpath +;; +;; 'require' and 'use' have additional options as described in their docs. ;; ;; scgilardi (gmail) -;; 7 April 2008 +;; 8 April 2008 ;; -;; load-system-resource is adapted from Stuart Sierra's public domain -;; require.clj. +;; Thanks to Stuart Sierra for providing many useful ideas, discussions +;; and code contributions for jewel.clj. (clojure/in-ns 'jewel) (clojure/refer 'clojure) @@ -50,50 +48,34 @@ ;; Private +(defmacro init-once + "Initializes a var exactly once. The var must already exist." + {:private true} + [var init] + `(let [v# (resolve '~var)] + (when-not (. v# (isBound)) + (. v# (bindRoot ~init))))) + (def #^{:private true :doc "A ref to a set of symbols representing loaded jewels"} *jewels*) +(init-once *jewels* (ref #{})) (def #^{:private true :doc "True while a verbose require is pending"} *verbose*) - -(defmacro init-once - "Initializes a var exactly once. The var must already exist." - #^{:private true} - [var init] - `(let [v# (resolve '~var)] - (when-not (. v# (isBound)) - (. v# (bindRoot ~init))))) - -(init-once *jewels* (ref #{})) (init-once *verbose* false) -(defn- load-system-resource - "Loads Clojure source from a resource within classpath" - ([res] - (let [url (. ClassLoader (getSystemResource res))] - (when-not url - (throw (new Exception (str "resource '" res - "' not found in classpath")))) - (if (= "file" (. url (getProtocol))) - (load-file (. url (getFile))) - (with-open reader - (new BufferedReader - (new InputStreamReader - (. url (openStream)))) - (load reader))))) - ([res in] - (load-system-resource (if in (str in \/ res) res)))) +(def load-system-resource) -(defn- jewel-load +(defn- load-one "Loads a jewel from <classpath>/in/" - [sym in] + [sym in need-ns] (let [res (str sym ".clj")] (load-system-resource res in) - (when-not (find-ns sym) + (when (and need-ns (not (find-ns sym))) (throw (new Exception (str "namespace '" sym "' not found after " "loading resource '" res "'"))))) (dosync @@ -101,25 +83,48 @@ (when *verbose* (println "loaded jewel" sym))) -(defn- jewel-load-all +(defn- load-all "Loads a jewel and any jewels on which it (directly or indirectly) depends even if already loaded." - [sym in] + [sym in need-ns] (dosync (commute *jewels* set/union (binding [*jewels* (ref #{})] - (jewel-load sym in) + (load-one sym in need-ns) @*jewels*)))) +(defn- require-one + "Single-argument version of 'require'." + [sym & options] + (let [opts (apply hash-map options) + in (:in opts) + need-ns (:need-ns opts) + reload (:reload opts) + reload-all (:reload-all opts) + verbose (:verbose opts)] + (binding [*verbose* (or *verbose* verbose)] + (cond reload-all + (load-all sym in need-ns) + (or reload (not (contains? @*jewels* sym))) + (load-one sym in need-ns))))) + +(defn- use-one + "Single-argument version of 'use'." + [sym & options] + (apply require-one sym :need-ns true options) + (apply refer sym options)) + ;; Public (defn require "Declares that subsequent code requires the capabilities - provided by the jewel named by sym. If the jewel is not yet - loaded, searches for it and loads it. The default search - is in the locations in classpath. Options may include any - filter arguments documented for clojure/refer and/or at most - one each of the following: + provided by the named jewels. Each argument is a quoted + symbol or a quoted list of the form (symbol & options...). + + If the jewel named by the symbol is not yet loaded + searches for it and loads it. The default search is in the + locations in classpath. Options may include at most one each + of the following: :in string :reload boolean @@ -130,22 +135,57 @@ directory relative to a location in classpath. When :reload is true, the jewel is reloaded if already loaded. When :reload-all is true, the jewel and all jewels on which - it directory or indirectly depends are reloaded. + it directly or indirectly depends are reloaded. When :verbose is true, prints a message after each load." - [sym & options] - (let [opts (apply hash-map options) - in (:in opts) - reload (:reload opts) - reload-all (:reload-all opts) - verbose (:verbose opts)] - (binding [*verbose* (or *verbose* verbose)] - (cond reload-all - (jewel-load-all sym in) - (or reload (not (contains? @*jewels* sym))) - (jewel-load sym in)))) - (apply refer sym options)) + [& args] + (doseq arg args + (if (symbol? arg) + (require-one arg) + (apply require-one arg)))) + +(defn use + "Requires and 'refer's the named jewels. Syntax is like that + of 'require', with additional options which are filters for + 'refer'." + [& args] + (doseq arg args + (if (symbol? arg) + (use-one arg) + (apply use-one arg)))) (defn jewels - "Returns a sorted sequence of symbols representing loaded jewels" + "Returns a sorted sequence of symbols naming loaded jewels" [] (sort @*jewels*)) + +(defn load-uri + "Loads Clojure source from a URI, which may be a java.net.URI + java.net.URL, or String. Accepts any URI scheme supported by + java.net.URLConnection (http and jar), plus file URIs." + [uri] + (let [url (cond ; coerce argument into java.net.URL + (instance? java.net.URL uri) uri + (instance? java.net.URI uri) (. uri (toURL)) + (string? uri) (new java.net.URL uri) + :else (throw (new Exception + (str "Cannot coerce " + (class uri) + " into java.net.URL."))))] + (if (= "file" (. url (getProtocol))) + (load-file (. url (getFile))) + (with-open reader + (new BufferedReader + (new InputStreamReader + (. url (openStream)))) + (load reader))))) + +(defn load-system-resource + "Loads Clojure source from a resource within classpath" + ([res] + (let [url (. ClassLoader (getSystemResource res))] + (when-not url + (throw (new Exception (str "resource '" res + "' not found in classpath")))) + (load-uri url))) + ([res in] + (load-system-resource (if in (str in \/ res) res)))) |