Permalink
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...
1 parent c1ff9fc commit 4850d4aa3b5b4c990be0958065dcf6b94bb55004 @stuartsierra stuartsierra committed Jul 27, 2012
@@ -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]))
@@ -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)))
@@ -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))))
+
@@ -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,65 +52,26 @@
;;; 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
source files; returns the symbol names of the declared namespaces."
[^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)))
@@ -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)))
Oops, something went wrong.

0 comments on commit 4850d4a

Please sign in to comment.