aboutsummaryrefslogtreecommitdiff
path: root/pkg.clj
blob: bbe4438b7720fc462dbb67ff8b58cc2e89738018 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
;;  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.
;;
;;  pkg.clj
;;
;;  Clojure package loading and dependency via require/provide.
;;
;;  A 'package' is a named set of capabilities represented by a symbol.
;;  The capabilities are usually defined in an implementation file
;;  somewhere in CLASSPATH whose name is the package name followed by
;;  ".clj".  The implementation file may be in the filesystem, in a jar
;;  file, or at any other valid CLASSPATH URL.
;;
;;  A call to 'require' indicates that subsequent code depends on
;;  capabilities provided by one or more packages and loads any of those
;;  packages that have not already been provided.
;;
;;  A call to 'provide' records that a package's capabilities are loaded.
;;  By default this is done automatically when 'require' loads the
;;  package's implementation file. 'provide' is also available separately
;;  to allow flexibility in making a package's capabilities available by
;;  some means other than the default.
;;
;;  pkg.clj provides variations of 'require' that:
;;
;;    - require a package and automatically 'refer' to a namespace of the
;;      same name.  This is for the common case of a package defining its
;;      own namespace which dependent code wants to use without namespace
;;      qualifiers. 'require-ns'
;;
;;    - force the loading of all packages and dependencies regardless of
;;      whether they have already been provided. 'require-force'
;;
;;    - both refer to the package's namespace and force a reload of the
;;      the package and its dependencies. 'require-ns-force'
;;
;;  The "force" variations are useful during development.
;;
;;  scgilardi (gmail)
;;  2 April 2008
;;
;;  load-resource adapted from Stuart Sierra's public domain require.clj

(clojure/in-ns 'pkg)
(clojure/refer 'clojure)

;; Private

(def
 #^{:doc "A ref to a set of symbols representing provided packages"
 :private true}
 *packages* (ref #{}))

(defn- load-resource
  "Sequentially read and evaluate forms from a resource within CLASSPATH"
  [name]
  (let [url (. ClassLoader (getSystemResource name))]
    (when-not url
      (throw (new Exception (str \" name \" " not found in CLASSPATH"))))
    (if (= "file" (. url (getProtocol)))
      (load-file (. url (getFile)))
      (with-open reader (new java.io.BufferedReader
                             (new java.io.InputStreamReader
                                  (. url (openStream))))
        (load reader)))))

;; Public

(defn packages
  "Returns a set of symbols representing provided packages"
  []
  @*packages*)

(defn provided?
  "Returns true if package has been provided, else false"
  [package]
  (contains? @*packages* package))

(defn provide
  "Marks a package as provided"
  [package]
  (dosync
   (commute *packages* conj package)
   nil))

(defn require
  "Indicates that subsequent code depends on capabilities provided by
  the specified packages. Loads any of those packages that have not
  yet been provided."
  [& packages]
  (doseq package packages
    (when-not (provided? package)
      (let [resource (str package ".clj")]
        (load-resource resource)
        (provide package)))))

(defn require-ns
  "Requires a package and then refers to the namespace of the same name
  with filters"
  [package & filters]
  (require package)
  (apply refer package filters))

(defn require-force
  "Like 'require' but will reload packages (and their dependencies) that
  have already been provided"
  [& packages]
  (let [forced-packages
        (binding [*packages* (ref #{})]
          (apply require packages)
          @*packages*)]
    (dosync
     (commute *packages* set/union forced-packages)))
  nil)

(defn require-ns-force
  "Like 'require-ns' but will reload packages (and their dependencies)
  that have already been provided"
  [package & filters]
  (require-force package)
  (apply refer package filters))

(provide 'pkg)