Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Refactor to independent namespaces with minimal dependencies

This is the first commit to break compatibility with tools.namespace
versions 0.1.x. The namespace "clojure.tools.namespace" no longer
exists; its functions have been split into c.t.namespace.parse,
c.t.namespace.find, c.t.namespace.file, and c.t.namespace.dir.
  • Loading branch information...
commit 4850d4aa3b5b4c990be0958065dcf6b94bb55004 1 parent c1ff9fc
@stuartsierra stuartsierra authored
View
13 src/main/clojure/clojure/tools/namespace/dependency.clj
@@ -1,5 +1,14 @@
-(ns clojure.tools.namespace.dependency
- "Bidirectional graphs of dependencies and dependent objects."
+;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and
+;; distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html 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.
+
+(ns ^{:author "Stuart Sierra"
+ :doc "Bidirectional graphs of dependencies and dependent objects."}
+ clojure.tools.namespace.dependency
(:refer-clojure :exclude (keys))
(:require [clojure.set :as set]))
View
63 src/main/clojure/clojure/tools/namespace/dir.clj
@@ -0,0 +1,63 @@
+;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and
+;; distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html 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.
+
+(ns ^{:author "Stuart Sierra"
+ :doc "Track namespace dependencies and changes by monitoring
+ file-modification timestamps"}
+ clojure.tools.namespace.dir
+ (:require [clojure.tools.namespace.file :as file]
+ [clojure.tools.namespace.track :as track]
+ [clojure.java.io :as io]
+ [clojure.set :as set]
+ [clojure.string :as string])
+ (:import (java.io File) (java.util.regex Pattern)))
+
+(defn- find-files [dirs]
+ (->> dirs
+ (map io/file)
+ (filter #(.exists %))
+ (mapcat file-seq)
+ (filter file/clojure-file?)
+ (map #(.getCanonicalFile %))))
+
+(defn- modified-files [tracker files]
+ (filter #(< (::time tracker 0) (.lastModified %)) files))
+
+(defn- deleted-files [tracker files]
+ (set/difference (::files tracker #{}) (set files)))
+
+(defn- update-files [tracker deleted modified]
+ (let [now (System/currentTimeMillis)]
+ (-> tracker
+ (update-in [::files] #(if % (apply disj % deleted) #{}))
+ (file/remove-files deleted)
+ (update-in [::files] into modified)
+ (file/add-files modified)
+ (assoc ::time now))))
+
+(defn- dirs-on-classpath []
+ (filter #(.isDirectory %)
+ (map #(File. %)
+ (string/split
+ (System/getProperty "java.class.path")
+ (Pattern/compile (Pattern/quote File/pathSeparator))))))
+
+(defn scan
+ "Scan directories for files which have changed since the last time
+ 'scan' was run; update the dependency tracker with
+ new/changed/deleted files.
+
+ If no dirs given, defaults to all directories on the classpath."
+ [tracker & dirs]
+ (let [ds (or (seq dirs) (dirs-on-classpath))
+ files (find-files ds)
+ deleted (seq (deleted-files tracker files))
+ modified (seq (modified-files tracker files))]
+ (if (or deleted modified)
+ (update-files tracker deleted modified)
+ tracker)))
View
61 src/main/clojure/clojure/tools/namespace/file.clj
@@ -0,0 +1,61 @@
+;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and
+;; distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html 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.
+
+(ns ^{:author "Stuart Sierra"
+ :doc "Read and track namespace information from files"}
+ clojure.tools.namespace.file
+ (:require [clojure.java.io :as io]
+ [clojure.tools.namespace.parse :as parse]
+ [clojure.tools.namespace.track :as track])
+ (:import (java.io PushbackReader)))
+
+(defn read-file-ns-decl
+ "Attempts to read a (ns ...) declaration from file, and returns the
+ unevaluated form. Returns nil if read fails, or if the first form
+ is not a ns declaration."
+ [file]
+ (with-open [rdr (PushbackReader. (io/reader file))]
+ (parse/read-ns-decl rdr)))
+
+(defn clojure-file?
+ "Returns true if the java.io.File represents a normal Clojure source
+ file."
+ [file]
+ (and (.isFile file)
+ (.endsWith (.getName file) ".clj")))
+
+;;; Dependency tracker
+
+(defn- files-and-deps [files]
+ (reduce (fn [m file]
+ (if-let [decl (read-file-ns-decl file)]
+ (let [deps (parse/deps-from-ns-decl decl)
+ name (second decl)]
+ (-> m
+ (assoc-in [:depmap name] deps)
+ (assoc-in [:filemap file] name)))
+ m))
+ {} files))
+
+(defn add-files
+ "Reads ns declarations from files; returns an updated dependency
+ tracker with those files added."
+ [tracker files]
+ (let [{:keys [depmap filemap]} (files-and-deps files)]
+ (-> tracker
+ (track/add depmap)
+ (update-in [::filemap] (fnil merge {}) filemap))))
+
+(defn remove-files
+ "Returns an updated dependency tracker with files removed. The files
+ must have been previously added with add-files."
+ [tracker files]
+ (-> tracker
+ (track/remove (keep (::filemap tracker {}) files))
+ (update-in [::filemap] #(apply dissoc % files))))
+
View
90 src/main/clojure/clojure/tools/namespace.clj → .../clojure/clojure/tools/namespace/find.clj
@@ -1,23 +1,19 @@
-;;; find_namespaces.clj: search for ns declarations in dirs, JARs, or CLASSPATH
-
-;; by Stuart Sierra, http://stuartsierra.com/
-;; April 19, 2009
-
-;; Copyright (c) Stuart Sierra, 2009. All rights reserved. The use
-;; and distribution terms for this software are covered by the Eclipse
+;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and
+;; distribution terms for this software are covered by the Eclipse
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html 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
+;; 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.
-
(ns
^{:author "Stuart Sierra",
:doc "Search for ns declarations in dirs, JARs, or CLASSPATH"}
- clojure.tools.namespace
+ clojure.tools.namespace.find
(:require [clojure.java.io :as io]
- [clojure.set :as set])
+ [clojure.set :as set]
+ [clojure.tools.namespace.file :as file]
+ [clojure.tools.namespace.parse :as parse])
(import (java.io File FileReader BufferedReader PushbackReader
InputStreamReader)
(java.util.jar JarFile JarEntry)))
@@ -56,57 +52,19 @@
;;; Finding namespaces in a directory tree
-(defn clojure-source-file?
- "Returns true if file is a normal file with a .clj extension."
- [^File file]
- (and (.isFile file)
- (.endsWith (.getName file) ".clj")))
-
(defn find-clojure-sources-in-dir
"Searches recursively under dir for Clojure source files (.clj).
Returns a sequence of File objects, in breadth-first sort order."
[^File dir]
;; Use sort by absolute path to get breadth-first search.
(sort-by #(.getAbsolutePath ^File %)
- (filter clojure-source-file? (file-seq dir))))
-
-(defn comment?
- "Returns true if form is a (comment ...)"
- [form]
- (and (list? form) (= 'comment (first form))))
-
-(defn ns-decl?
- "Returns true if form is a (ns ...) declaration."
- [form]
- (and (list? form) (= 'ns (first form))))
-
-(defn read-ns-decl
- "Attempts to read a (ns ...) declaration from rdr, and returns the
- unevaluated form. Returns nil if read fails or if a ns declaration
- cannot be found. The ns declaration must be the first Clojure form
- in the file, except for (comment ...) forms."
- [^PushbackReader rdr]
- (try
- (loop [] (let [form (doto (read rdr) str)]
- (cond
- (ns-decl? form) form
- (comment? form) (recur)
- :else nil)))
- (catch Exception e nil)))
-
-(defn read-file-ns-decl
- "Attempts to read a (ns ...) declaration from file, and returns the
- unevaluated form. Returns nil if read fails, or if the first form
- is not a ns declaration."
- [file]
- (with-open [rdr (PushbackReader. (io/reader file))]
- (read-ns-decl rdr)))
+ (filter file/clojure-file? (file-seq dir))))
(defn find-ns-decls-in-dir
"Searches dir recursively for (ns ...) declarations in Clojure
source files; returns the unevaluated ns declarations."
[^File dir]
- (keep read-file-ns-decl (find-clojure-sources-in-dir dir)))
+ (keep file/read-file-ns-decl (find-clojure-sources-in-dir dir)))
(defn find-namespaces-in-dir
"Searches dir recursively for (ns ...) declarations in Clojure
@@ -114,7 +72,6 @@
[^File dir]
(map second (find-ns-decls-in-dir dir)))
-
;;; Finding namespaces in JAR files
(defn clojure-sources-in-jar
@@ -130,7 +87,7 @@
(with-open [rdr (PushbackReader.
(io/reader
(.getInputStream jarfile (.getEntry jarfile entry-name))))]
- (read-ns-decl rdr)))
+ (parse/read-ns-decl rdr)))
(defn find-ns-decls-in-jarfile
"Searches the JAR file for Clojure source files containing (ns ...)
@@ -169,28 +126,3 @@
[files]
(map second (find-ns-decls files)))
-;;; Parsing dependencies
-
-(defn- deps-from-libspec [prefix form]
- (cond (list? form) (apply set/union (map (fn [f] (deps-from-libspec
- (symbol (str (when prefix (str prefix "."))
- (first form)))
- f))
- (rest form)))
- (vector? form) (deps-from-libspec prefix (first form))
- (symbol? form) #{(symbol (str (when prefix (str prefix ".")) form))}
- (keyword? form) #{}
- :else (throw (IllegalArgumentException.
- (pr-str "Unparsable namespace form:" form)))))
-
-(defn- deps-from-ns-form [form]
- (when (and (list? form)
- (contains? #{:use :require} (first form)))
- (apply set/union (map #(deps-from-libspec nil %) (rest form)))))
-
-(defn deps-from-ns-decl
- "Given an (ns...) declaration form (unevaluated), returns a set of
- symbols naming the dependencies of that namespace. Handles :use and
- :require clauses but not :load."
- [decl]
- (apply set/union (map deps-from-ns-form decl)))
View
70 src/main/clojure/clojure/tools/namespace/parse.clj
@@ -0,0 +1,70 @@
+;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and
+;; distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html 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.
+
+(ns ^{:author "Stuart Sierra"
+ :doc "Parse Clojure namespace (ns) declarations and extract
+ dependencies."}
+ clojure.tools.namespace.parse
+ (:require [clojure.set :as set]))
+
+(defn comment?
+ "Returns true if form is a (comment ...)"
+ [form]
+ (and (list? form) (= 'comment (first form))))
+
+(defn ns-decl?
+ "Returns true if form is a (ns ...) declaration."
+ [form]
+ (and (list? form) (= 'ns (first form))))
+
+(defn read-ns-decl
+ "Attempts to read a (ns ...) declaration from a
+ java.io.PushbackReader, and returns the unevaluated form. Returns
+ nil if read fails or if a ns declaration cannot be found. The ns
+ declaration must be the first Clojure form in the file, except for
+ (comment ...) forms."
+ [rdr]
+ (try
+ (loop [] (let [form (doto (read rdr) str)]
+ (cond
+ (ns-decl? form) form
+ (comment? form) (recur)
+ :else nil)))
+ (catch Exception e nil)))
+
+;;; Parsing dependencies
+
+(defn- deps-from-libspec [prefix form]
+ (cond (list? form)
+ (apply set/union
+ (map (fn [f] (deps-from-libspec
+ (symbol (str (when prefix (str prefix "."))
+ (first form)))
+ f))
+ (rest form)))
+ (vector? form)
+ (deps-from-libspec prefix (first form))
+ (symbol? form)
+ #{(symbol (str (when prefix (str prefix ".")) form))}
+ (keyword? form)
+ #{}
+ :else
+ (throw (IllegalArgumentException.
+ (pr-str "Unparsable namespace form:" form)))))
+
+(defn- deps-from-ns-form [form]
+ (when (and (list? form)
+ (contains? #{:use :require} (first form)))
+ (apply set/union (map #(deps-from-libspec nil %) (rest form)))))
+
+(defn deps-from-ns-decl
+ "Given an (ns...) declaration form (unevaluated), returns a set of
+ symbols naming the dependencies of that namespace. Handles :use and
+ :require clauses but not :load."
+ [decl]
+ (apply set/union (map deps-from-ns-form decl)))
View
87 src/main/clojure/clojure/tools/namespace/poll.clj
@@ -1,87 +0,0 @@
-(ns clojure.tools.namespace.poll
- (:require [clojure.tools.namespace.tracker :as tracker]
- [clojure.java.io :as io]
- [clojure.set :as set])
- (:import (java.util.concurrent Executors ThreadFactory TimeUnit)))
-
-(defn before [state]
- (let [{:keys [error time load unload time]} state
- date (doto (java.util.GregorianCalendar.)
- (.setTimeInMillis time))]
- (prn (array-map :unload unload :reload load :time date))))
-
-(defn after [state]
- (let [{:keys [error load]} state]
- (when error
- (prn (array-map :error error :pending load)))))
-
-(defn clojure-file? [file]
- (and (.isFile file)
- (.endsWith (.getName file) ".clj")))
-
-(defn find-files [state]
- (->> (:dirs state)
- (map io/file)
- (filter #(.exists %))
- (mapcat file-seq)
- (filter clojure-file?)
- (map #(.getCanonicalFile %))))
-
-(defn modified-files [state files]
- (filter #(< (:time state) (.lastModified %)) files))
-
-(defn deleted-files [state files]
- (set/difference (:files state) (set files)))
-
-(defn watcher [& options]
- (merge {:dirs ["src" "test"]
- :time 0
- :files #{}}
- (tracker/tracker)
- (apply hash-map options)))
-
-(defn update-reload [state deleted modified]
- (let [now (System/currentTimeMillis)]
- (-> state
- (update-in [:files] #(apply disj % deleted))
- (tracker/remove-files deleted)
- (update-in [:files] into modified)
- (tracker/add-files modified)
- (assoc :time now)
- (doto before)
- tracker/reload
- (doto after))))
-
-(defn poll-reload [state]
- (let [files (find-files state)
- deleted (seq (deleted-files state files))
- modified (seq (modified-files state files))]
- (if (or deleted modified)
- (update-reload state deleted modified)
- state)))
-
-(def default-delay 500)
-
-(let [a (atom -1)]
- (defn- default-executor []
- (Executors/newSingleThreadScheduledExecutor
- (reify ThreadFactory
- (newThread [this r]
- (Thread. r (str "clojure.tools.namespace.watch executor "
- (swap! a inc))))))))
-
-(defn poll [& options]
- (let [{:keys [watcher delay executor]
- :or {watcher (watcher)
- delay default-delay
- executor (default-executor)}} options
- a (atom watcher)
- f (bound-fn []
- (try (reset! a (poll-reload @a))
- (catch Throwable t
- (binding [*out* (java.io.PrintWriter. *err*)]
- (println "ERROR in scheduled task"
- "from clojure.tools.namespace.watch")
- (.printStackTrace t *out*)))))]
- (.scheduleWithFixedDelay executor f
- 0 delay TimeUnit/MILLISECONDS)))
View
53 src/main/clojure/clojure/tools/namespace/reload.clj
@@ -0,0 +1,53 @@
+;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and
+;; distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html 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.
+
+(ns ^{:author "Stuart Sierra"
+ :doc "Force reloading namespaces on demand or through a
+ dependency tracker"}
+ clojure.tools.namespace.reload
+ (:require [clojure.tools.namespace.track :as track]))
+
+(defn remove-lib
+ "Remove lib's namespace and remove lib from the set of loaded libs."
+ [lib]
+ (remove-ns lib)
+ (dosync (alter @#'clojure.core/*loaded-libs* disj lib)))
+
+(defn track-reload-one
+ "Executes the next pending unload/reload operation in the dependency
+ tracker. Returns the updated dependency tracker. If reloading caused
+ an error, it is captured as ::error."
+ [tracker]
+ (let [{unload ::track/unload, load ::track/load} tracker]
+ (cond
+ (seq unload)
+ (let [n (first unload)]
+ (prn :unloading n)
+ (remove-lib n)
+ (update-in tracker [::track/unload] rest))
+ (seq load)
+ (let [n (first load)]
+ (try (prn :loading n)
+ (require n :reload)
+ (update-in tracker [::track/load] rest)
+ (catch Throwable t
+ (assoc tracker ::error t ::track/unload load))))
+ :else
+ tracker)))
+
+(defn track-reload
+ "Executes all pending unload/reload operations on dependency tracker
+ until either an error is encountered or there are no more pending
+ operations."
+ [tracker]
+ (loop [tracker (dissoc tracker ::error)]
+ (let [{error ::error, unload ::track/unload, load ::track/load} tracker]
+ (if (and (not error)
+ (or (seq load) (seq unload)))
+ (recur (track-reload-one tracker))
+ tracker))))
View
110 src/main/clojure/clojure/tools/namespace/track.clj
@@ -0,0 +1,110 @@
+;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and
+;; distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html 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.
+
+(ns ^{:author "Stuart Sierra"
+ :doc "Dependency tracker which can compute which namespaces need to be
+ reloaded after files have changed. This is the low-level
+ implementation that requires you to find the namespace dependencies
+ yourself: most users will interact with the wrappers in
+ clojure.tools.namespace.file and clojure.tools.namespace.dir."}
+ clojure.tools.namespace.track
+ (:refer-clojure :exclude (remove))
+ (:require [clojure.set :as set]
+ [clojure.tools.namespace.dependency :as dep]))
+
+(defn- remove-deps [deps names]
+ (reduce dep/remove-key deps names))
+
+(defn- add-deps [deps depmap]
+ (reduce (fn [ds [name dependencies]]
+ (reduce (fn [g dep] (dep/depend g name dep))
+ ds dependencies))
+ deps depmap))
+
+(defn- update-deps [deps depmap]
+ (-> deps
+ (remove-deps (keys depmap))
+ (add-deps depmap)))
+
+(defn- affected-namespaces [deps names]
+ (apply set/union
+ (set names)
+ (map #(dep/transitive-dependents deps %) names)))
+
+(defn add
+ "Returns an updated dependency tracker with new/updated namespaces.
+
+ Depmap is a map describing the new or modified namespaces. Keys in
+ the map are namespace names (symbols). Values in the map are sets of
+ symbols naming the birect dependencies of each namespace. For
+ example, assuming these ns declarations:
+
+ (ns alpha (:require beta))
+ (ns beta (:require gamma delta))
+
+ the depmap would look like this:
+
+ {alpha #{beta}
+ beta #{gamma delta}}
+
+ After adding new/updated namespaces, the dependency tracker will
+ have two lists associated with the following keys:
+
+ :clojure.tools.namespace.track/unload
+ is the list of namespaces that need to be removed
+
+ :clojure.tools.namespace.track/load
+ is the list of namespaces that need to be reloaded
+
+ To reload namespaces in the correct order, first remove/unload all
+ namespaces in the 'unload' list, then (re)load all namespaces in
+ the 'load' list."
+ [tracker depmap]
+ (let [{load ::load
+ unload ::unload
+ deps ::deps
+ :or {load (), unload (), deps (dep/graph)}} tracker
+ new-deps (update-deps deps depmap)
+ changed (affected-namespaces new-deps (keys depmap))]
+ (assoc tracker
+ ::deps new-deps
+ ::unload (distinct
+ (concat (reverse (sort (dep/topo-comparator deps) changed))
+ unload))
+ ::load (distinct
+ (concat (sort (dep/topo-comparator new-deps) changed)
+ load)))))
+
+(defn remove
+ "Returns an updated dependency tracker from which the namespaces
+ (symbols) have been removed. The :unload and :load lists indicate
+ the order in which namespaces should be un/reloaded to reflect this
+ change, as with add."
+ [tracker names]
+ (let [{load ::load
+ unload ::unload
+ deps ::deps
+ :or {load (), unload (), deps (dep/graph)}} tracker
+ known (set (dep/keys deps))
+ removed-names (filter known names)
+ new-deps (remove-deps deps removed-names)
+ changed (affected-namespaces deps removed-names)]
+ (assoc tracker
+ ::deps new-deps
+ ::unload (distinct
+ (concat (reverse (sort (dep/topo-comparator deps) changed))
+ unload))
+ ::load (distinct
+ (filter (complement (set removed-names))
+ (concat (sort (dep/topo-comparator new-deps) changed)
+ load))))))
+
+(defn tracker
+ "Returns a new, empty dependency tracker"
+ []
+ (hash-map))
View
158 src/main/clojure/clojure/tools/namespace/tracker.clj
@@ -1,158 +0,0 @@
-(ns clojure.tools.namespace.tracker
- (:require [clojure.set :as set]
- [clojure.tools.namespace :as tns]
- [clojure.tools.namespace.dependency :as dep]))
-
-(defn- remove-deps [deps names]
- (reduce dep/remove-key deps names))
-
-(defn- add-deps [deps depmap]
- (reduce (fn [ds [name dependencies]]
- (reduce (fn [g dep] (dep/depend g name dep))
- ds dependencies))
- deps depmap))
-
-(defn- update-deps [deps depmap]
- (-> deps
- (remove-deps (keys depmap))
- (add-deps depmap)))
-
-(defn- affected-namespaces [deps names]
- (apply set/union
- (set names)
- (map #(dep/transitive-dependents deps %) names)))
-
-(defn tracker
- "Returns a new namespace dependency tracker."
- []
- {:deps (dep/graph)
- :unload ()
- :load ()})
-
-(defn add
- "Returns an updated dependency tracker with new/updated namespaces.
-
- Depmap is a map describing the new or modified namespaces. Keys in
- the map are namespace names (symbols). Values in the map are sets of
- symbols naming the birect dependencies of each namespace. For
- example, assuming these ns declarations:
-
- (ns alpha (:require beta))
- (ns beta (:require gamma delta))
-
- the depmap would look like this:
-
- {alpha #{beta}
- beta #{gamma delta}}
-
- After adding new/updated namespaces, the dependency tracker will
- have two lists:
-
- :unload is the list of namespaces that need to be removed
-
- :load is the list of namespaces that need to be reloaded
-
- To reload namespaces in the correct order, first remove/unload all
- namespaces in the :unload list, then (re)load all namespaces in
- the :load list.
-"
- [state depmap]
- (let [{:keys [load unload deps nsmap]} state
- new-deps (update-deps deps depmap)
- changed (affected-namespaces new-deps (keys depmap))]
- (assoc state
- :deps new-deps
- :unload (distinct
- (concat (reverse (sort (dep/topo-comparator deps) changed))
- unload))
- :load (distinct
- (concat (sort (dep/topo-comparator new-deps) changed)
- load)))))
-
-(defn add-files
- "Reads ns declarations from files and adds them to the dependency
- tracker."
- [state files]
- (let [{:keys [depmap filemap]}
- (reduce (fn [m file]
- (if-let [decl (tns/read-file-ns-decl file)]
- (let [deps (tns/deps-from-ns-decl decl)
- name (second decl)]
- (-> m
- (assoc-in [:depmap name] deps)
- (assoc-in [:filemap file] name)))
- m))
- {} files)]
- (-> state
- (add depmap)
- (update-in [:filemap] (fnil merge {}) filemap))))
-
-(defn remove-names
- "Returns an updated dependency tracker from which the namespaces
- (symbols) have been removed. The :unload and :load lists indicate
- the order in which namespaces should be un/reloaded to reflect this
- change, as with add."
- [state names]
- (let [{:keys [load unload deps]} state
- known (set (dep/keys deps))
- removed-names (filter known names)
- new-deps (remove-deps deps removed-names)
- changed (affected-namespaces deps removed-names)]
- (assoc state
- :deps new-deps
- :unload (distinct
- (concat (reverse (sort (dep/topo-comparator deps) changed))
- unload))
- :load (distinct
- (filter (complement (set removed-names))
- (concat (sort (dep/topo-comparator new-deps) changed)
- load))))))
-
-(defn remove-files
- "Returns an updated dependency tracker after removing files. The
- files must have been previously added to the dependency tracker
- using add-files."
- [state files]
- (-> state
- (remove-names (keep (:filemap state {}) files))
- (update-in [:filemap] #(apply dissoc % files))))
-
-(defn remove-lib
- "Remove lib's namespace and remove lib from the set of loaded libs."
- [lib]
- (remove-ns lib)
- (dosync (alter @#'clojure.core/*loaded-libs* disj lib)))
-
-(defn reload-one
- "Executes the next pending unload/reload operation in the dependency
- tracker. Returns the updated dependency tracker. If reloading caused
- an error, it is captured as :error."
- [state]
- (let [{:keys [unload load]} state]
- (cond
- (seq unload)
- (let [n (first unload)]
- (prn :unloading n)
- (remove-lib n)
- (update-in state [:unload] rest))
- (seq load)
- (let [n (first load)]
- (try (prn :loading n)
- (require n :reload)
- (update-in state [:load] rest)
- (catch Throwable t
- (assoc state :error t :unload [n]))))
- :else
- state)))
-
-(defn reload
- "Executes all pending unload/reload operations on dependency tracker
- until either an error is encountered or there are no more pending
- operations."
- [state]
- (loop [state (dissoc state :error)]
- (let [{:keys [unload load error]} state]
- (if (and (or (seq load) (seq unload))
- (not error))
- (recur (reload-one state))
- state))))
Please sign in to comment.
Something went wrong with that request. Please try again.