diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..7dec2d3 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,194 @@ +# Change Log for tools.namespace + + +## 0.3.x series + +### Version 0.3.0 + + * **ClojureScript Analysis Support** [TNS-35] + + * Platform-neutral namespaces were converted to conditional-read + (`.cljc`) files: c.t.n.dependency, c.t.n.track, and + c.t.n.parse. These namespaces can be used in ClojureScript + programs. + + * Added support for finding, parsing, and analyzing + ClojureScript source files (`.cljs` and `.cljc`) in + c.t.n.file, c.t.n.parse, c.t.n.dir, and c.t.n.find with + optional "platform" arguments. These namespaces still only + **run** on the Clojure(JVM) platform. + + * Reloading and interactive features remain Clojure(JVM) only + for now: c.t.n.move, c.t.n.reload, and c.t.n.repl + + * Uses [tools.reader] for platform-independent parsing and + conditional-reader support. + + * Enhancement [TNS-36]: Use java.classpath for better JVM classpath + resolution + + * Minimum Clojure version is 1.7.0 + + * Some definitions deprecated; see source code or Var metadata for + details. + + + +## 0.2.x series + +### Version 0.2.11 on 19-Jun-2015 + + * [TNS-34] Allow reader conditionals in parsed source files + +### Version 0.2.10 on 26-Feb-2015 + + * Widen existing functions to handle both clj and cljc files in + advance of reader conditional support in Clojure 1.7. + +### Version 0.2.9 on 31-Jan-2015 + + * Fix [TNS-20]: Undefined 'unload' order after namespaces are first + added to an new, empty tracker. + + * Improvement [TNS-21]: Support `ns` clauses which use vectors + instead of lists for clauses, contrary to docs. + + * Improvement [TNS-32]: Support `ns` clauses which use symbols as + clause heads instead of keywords, contrary to docs. + +### Version 0.2.8 on 19-Dec-2014 + + * Improvement [TNS-31]: Specific error message when `:after` symbol + passed to `refresh` cannot be resolved. + + * Fix [TNS-26]: namespace alias recovery after failed reload did not + work due to local binding shadowing global Var + +### Version 0.2.7 on 19-Sept-2014 + + * [Revert bad commit](https://github.com/clojure/tools.namespace/commit/27194f2edfe3f5f9e1343f993beca4b43f0bafe8) + mistakenly included in 0.2.6 which could cause the tracker's + 'unload' order to be incorrect. See discussion at [TNS-20]. + +### **BROKEN** Version 0.2.6 on 7-Sept-2014 **DO NOT USE** + + * `clojure.tools.namespace.parse/read-ns-decl` asserts that its + argument is a PushbackReader, instead of silently returning nil + + * Fix [TNS-22]: broken `clojure.string/replace` with Windows path + separator + +### Version 0.2.5 on 15-Jul-2014 + + * New `clojure.tools.namespace.repl/clear` empties the state of the + REPL dependency tracker. This can help repair the dependency + tracker after a failed load or a circular dependency error. + + * Enhancement [TNS-19]: `deps-from-ns-decl` should return an empty + set instead of nil. This may be a breaking change for some but + is consistent with the original docstring. + + * Enhancement [TNS-18]: Compute transitive dependencies in linear time. + + * Enhancement [TNS-17]: The `ns` form doesn't need to be the first + top-level form in a file. + + * Fix [TNS-16]: Don't depend on specific hash ordering in tests. + Exposed by new hash functions in Clojure 1.6.0. + + * Fix [TNS-15]: Handle spaces in classpath directories (old + `clojure.tools.namespace`) + + * Fix [TNS-12]: Duplicate definition of `jar-file?` + +### Version 0.2.4 on 19-Jul-2013 + + * Fix [TNS-10]: Forbid circular dependency when a namespace depends + on itself + + * Fix [TNS-9] and [TNS-11]: support other prefix-list forms + + * Fix [TNS-8]: In `move-ns`, do not modify files whose contents does + not change + +### Version 0.2.3 on 01-Apr-2013 + + * New: Attempt recovery of aliases/refers in REPL after error + +### Version 0.2.2 on 14-Dec-2012 + + * New: Add `:after` option to `refresh` + + * New: Add `clojure.tools.namespace.move` + + * Fix [TNS-4], reflection warnings + +### Version 0.2.1 on 26-Oct-2012 + + * Fix: Restore deprecated 0.1.x APIs in `clojure.tools.namespace` + + * Fix [TNS-3], actually use `refresh-dirs` + +### **BROKEN** Version 0.2.0 on 05-Oct-2012 **DO NOT USE** + + * Not recommended for use: this release introduced breaking API + changes (renaming core namespaces and functions) without + backwards-compatibility. Applications with dependencies on both + the 0.2.x and 0.1.x APIs cannot use this version. + + * New dependency tracking & reloading features + + * Eliminate dependency on [java.classpath] + + + +## 0.1.x series + +### Version 0.1.3 on 24-Apr-2012 + + * Fix [TNS-1] Workaround for Clojure 1.2 reader bug + +### Version 0.1.2 on 10-Feb-2012 + + * Fix: Eliminate reflection warnings + +### Version 0.1.1 on 18-May-2011 + +### Version 0.1.0 on 24-Apr-2011 + + * Source-compatible with clojure.contrib.find-namespaces in old + clojure-contrib 1.2.0 + +[TNS-1]: http://dev.clojure.org/jira/browse/TNS-1 +[TNS-3]: http://dev.clojure.org/jira/browse/TNS-3 +[TNS-4]: http://dev.clojure.org/jira/browse/TNS-4 +[TNS-8]: http://dev.clojure.org/jira/browse/TNS-8 +[TNS-9]: http://dev.clojure.org/jira/browse/TNS-9 +[TNS-10]: http://dev.clojure.org/jira/browse/TNS-10 +[TNS-11]: http://dev.clojure.org/jira/browse/TNS-11 +[TNS-12]: http://dev.clojure.org/jira/browse/TNS-12 +[TNS-15]: http://dev.clojure.org/jira/browse/TNS-15 +[TNS-16]: http://dev.clojure.org/jira/browse/TNS-16 +[TNS-17]: http://dev.clojure.org/jira/browse/TNS-17 +[TNS-18]: http://dev.clojure.org/jira/browse/TNS-18 +[TNS-19]: http://dev.clojure.org/jira/browse/TNS-19 +[TNS-20]: http://dev.clojure.org/jira/browse/TNS-20 +[TNS-21]: http://dev.clojure.org/jira/browse/TNS-21 +[TNS-22]: http://dev.clojure.org/jira/browse/TNS-22 +[TNS-23]: http://dev.clojure.org/jira/browse/TNS-23 +[TNS-24]: http://dev.clojure.org/jira/browse/TNS-24 +[TNS-25]: http://dev.clojure.org/jira/browse/TNS-25 +[TNS-26]: http://dev.clojure.org/jira/browse/TNS-26 +[TNS-27]: http://dev.clojure.org/jira/browse/TNS-27 +[TNS-28]: http://dev.clojure.org/jira/browse/TNS-28 +[TNS-29]: http://dev.clojure.org/jira/browse/TNS-29 +[TNS-30]: http://dev.clojure.org/jira/browse/TNS-30 +[TNS-31]: http://dev.clojure.org/jira/browse/TNS-31 +[TNS-32]: http://dev.clojure.org/jira/browse/TNS-32 +[TNS-33]: http://dev.clojure.org/jira/browse/TNS-33 +[TNS-34]: http://dev.clojure.org/jira/browse/TNS-34 +[TNS-35]: http://dev.clojure.org/jira/browse/TNS-35 +[TNS-36]: http://dev.clojure.org/jira/browse/TNS-36 +[java.classpath]: https://github.com/clojure/java.classpath +[tools.reader]: https://github.com/clojure/tools.reader +[JEP 122]: http://openjdk.java.net/jeps/122 diff --git a/README.md b/README.md index cc7761c..43e1479 100644 --- a/README.md +++ b/README.md @@ -533,165 +533,6 @@ Developer Information -Change Log ----------------------------------------- - -### Version 0.2.12-SNAPSHOT - - * In development, current Git master branch - -### Version 0.2.11 on 19-Jun-2015 - - * [TNS-34] Allow reader conditionals in parsed source files - -### Version 0.2.10 on 26-Feb-2015 - - * Widen existing functions to handle both clj and cljc files in - advance of reader conditional support in Clojure 1.7. - -### Version 0.2.9 on 31-Jan-2015 - - * Fix [TNS-20]: Undefined 'unload' order after namespaces are first - added to an new, empty tracker. - - * Improvement [TNS-21]: Support `ns` clauses which use vectors - instead of lists for clauses, contrary to docs. - - * Improvement [TNS-32]: Support `ns` clauses which use symbols as - clause heads instead of keywords, contrary to docs. - -### Version 0.2.8 on 19-Dec-2014 - - * Improvement [TNS-31]: Specific error message when `:after` symbol - passed to `refresh` cannot be resolved. - - * Fix [TNS-26]: namespace alias recovery after failed reload did not - work due to local binding shadowing global Var - -### Version 0.2.7 on 19-Sept-2014 - - * [Revert bad commit](https://github.com/clojure/tools.namespace/commit/27194f2edfe3f5f9e1343f993beca4b43f0bafe8) - mistakenly included in 0.2.6 which could cause the tracker's - 'unload' order to be incorrect. See discussion at [TNS-20]. - -### **BROKEN** Version 0.2.6 on 7-Sept-2014 **DO NOT USE** - - * `clojure.tools.namespace.parse/read-ns-decl` asserts that its - argument is a PushbackReader, instead of silently returning nil - - * Fix [TNS-22]: broken `clojure.string/replace` with Windows path - separator - -### Version 0.2.5 on 15-Jul-2014 - - * New `clojure.tools.namespace.repl/clear` empties the state of the - REPL dependency tracker. This can help repair the dependency - tracker after a failed load or a circular dependency error. - - * Enhancement [TNS-19]: `deps-from-ns-decl` should return an empty - set instead of nil. This may be a breaking change for some but - is consistent with the original docstring. - - * Enhancement [TNS-18]: Compute transitive dependencies in linear time. - - * Enhancement [TNS-17]: The `ns` form doesn't need to be the first - top-level form in a file. - - * Fix [TNS-16]: Don't depend on specific hash ordering in tests. - Exposed by new hash functions in Clojure 1.6.0. - - * Fix [TNS-15]: Handle spaces in classpath directories (old - `clojure.tools.namespace`) - - * Fix [TNS-12]: Duplicate definition of `jar-file?` - -### Version 0.2.4 on 19-Jul-2013 - - * Fix [TNS-10]: Forbid circular dependency when a namespace depends - on itself - - * Fix [TNS-9] and [TNS-11]: support other prefix-list forms - - * Fix [TNS-8]: In `move-ns`, do not modify files whose contents does - not change - -### Version 0.2.3 on 01-Apr-2013 - - * New: Attempt recovery of aliases/refers in REPL after error - -### Version 0.2.2 on 14-Dec-2012 - - * New: Add `:after` option to `refresh` - - * New: Add `clojure.tools.namespace.move` - - * Fix [TNS-4], reflection warnings - -### Version 0.2.1 on 26-Oct-2012 - - * Fix: Restore deprecated 0.1.x APIs in `clojure.tools.namespace` - - * Fix [TNS-3], actually use `refresh-dirs` - -### Version 0.2.0 on 05-Oct-2012 - - * **Not recommended for use**: this release introduced breaking API - changes (renaming core namespaces and functions) without - backwards-compatibility. Applications with dependencies on both - the 0.2.x and 0.1.x APIs cannot use this version. - - * New dependency tracking & reloading features - - * Eliminate dependency on [java.classpath] - -### Version 0.1.3 on 24-Apr-2012 - - * Fix [TNS-1] Workaround for Clojure 1.2 reader bug - -### Version 0.1.2 on 10-Feb-2012 - - * Fix: Eliminate reflection warnings - -### Version 0.1.1 on 18-May-2011 - -### Version 0.1.0 on 24-Apr-2011 - - * Source-compatible with clojure.contrib.find-namespaces in old - clojure-contrib 1.2.0 - -[TNS-1]: http://dev.clojure.org/jira/browse/TNS-1 -[TNS-3]: http://dev.clojure.org/jira/browse/TNS-3 -[TNS-4]: http://dev.clojure.org/jira/browse/TNS-4 -[TNS-8]: http://dev.clojure.org/jira/browse/TNS-8 -[TNS-9]: http://dev.clojure.org/jira/browse/TNS-9 -[TNS-10]: http://dev.clojure.org/jira/browse/TNS-10 -[TNS-11]: http://dev.clojure.org/jira/browse/TNS-11 -[TNS-12]: http://dev.clojure.org/jira/browse/TNS-12 -[TNS-15]: http://dev.clojure.org/jira/browse/TNS-15 -[TNS-16]: http://dev.clojure.org/jira/browse/TNS-16 -[TNS-17]: http://dev.clojure.org/jira/browse/TNS-17 -[TNS-18]: http://dev.clojure.org/jira/browse/TNS-18 -[TNS-19]: http://dev.clojure.org/jira/browse/TNS-19 -[TNS-20]: http://dev.clojure.org/jira/browse/TNS-20 -[TNS-21]: http://dev.clojure.org/jira/browse/TNS-21 -[TNS-22]: http://dev.clojure.org/jira/browse/TNS-22 -[TNS-23]: http://dev.clojure.org/jira/browse/TNS-23 -[TNS-24]: http://dev.clojure.org/jira/browse/TNS-24 -[TNS-25]: http://dev.clojure.org/jira/browse/TNS-25 -[TNS-26]: http://dev.clojure.org/jira/browse/TNS-26 -[TNS-27]: http://dev.clojure.org/jira/browse/TNS-27 -[TNS-28]: http://dev.clojure.org/jira/browse/TNS-28 -[TNS-29]: http://dev.clojure.org/jira/browse/TNS-29 -[TNS-30]: http://dev.clojure.org/jira/browse/TNS-30 -[TNS-31]: http://dev.clojure.org/jira/browse/TNS-31 -[TNS-32]: http://dev.clojure.org/jira/browse/TNS-32 -[TNS-33]: http://dev.clojure.org/jira/browse/TNS-33 -[TNS-34]: http://dev.clojure.org/jira/browse/TNS-34 -[java.classpath]: https://github.com/clojure/java.classpath -[JEP 122]: http://openjdk.java.net/jeps/122 - - - Copyright and License ---------------------------------------- diff --git a/pom.xml b/pom.xml index 80cc93a..b912e4e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,7 @@ 4.0.0 tools.namespace - 0.2.12-SNAPSHOT + 0.3.0-SNAPSHOT ${artifactId} @@ -10,6 +10,25 @@ 0.1.2 + + + org.clojure + clojure + 1.7.0 + provided + + + org.clojure + java.classpath + 0.2.2 + + + org.clojure + tools.reader + 0.9.2 + + + true @@ -26,4 +45,5 @@ git@github.com:clojure/tools.namespace.git HEAD + diff --git a/src/main/clojure/clojure/tools/namespace/dependency.clj b/src/main/clojure/clojure/tools/namespace/dependency.cljc similarity index 97% rename from src/main/clojure/clojure/tools/namespace/dependency.clj rename to src/main/clojure/clojure/tools/namespace/dependency.cljc index 96a5e9e..3c6b54a 100644 --- a/src/main/clojure/clojure/tools/namespace/dependency.clj +++ b/src/main/clojure/clojure/tools/namespace/dependency.cljc @@ -142,6 +142,10 @@ (remove-node g' node) (clojure.set/union (set more) (set add))))))) +(def ^:private max-number + #?(:clj Long/MAX_VALUE + :cljs js/Number.MAX_VALUE)) + (defn topo-comparator "Returns a comparator fn which produces a topological sort based on the dependencies in graph. Nodes not present in the graph will sort @@ -149,6 +153,5 @@ [graph] (let [pos (zipmap (topo-sort graph) (range))] (fn [a b] - (compare (get pos a Long/MAX_VALUE) - (get pos b Long/MAX_VALUE))))) - + (compare (get pos a max-number) + (get pos b max-number))))) diff --git a/src/main/clojure/clojure/tools/namespace/dir.clj b/src/main/clojure/clojure/tools/namespace/dir.clj index 9f9b0f1..e12288d 100644 --- a/src/main/clojure/clojure/tools/namespace/dir.clj +++ b/src/main/clojure/clojure/tools/namespace/dir.clj @@ -11,19 +11,19 @@ file-modification timestamps"} clojure.tools.namespace.dir (:require [clojure.tools.namespace.file :as file] + [clojure.tools.namespace.find :as find] [clojure.tools.namespace.track :as track] + [clojure.java.classpath :refer [classpath-directories]] [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] +(defn- find-files [dirs platform] (->> dirs (map io/file) (filter #(.exists ^File %)) - (mapcat file-seq) - (filter file/clojure-file?) - (map #(.getCanonicalFile ^File %)))) + (mapcat #(find/find-sources-in-dir % platform)))) (defn- modified-files [tracker files] (filter #(< (::time tracker 0) (.lastModified ^File %)) files)) @@ -31,43 +31,84 @@ (defn- deleted-files [tracker files] (set/difference (::files tracker #{}) (set files))) -(defn- update-files [tracker deleted modified] +(defn- update-files [tracker deleted modified {:keys [read-opts]}] (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) + (file/add-files modified read-opts) (assoc ::time now)))) -(defn- dirs-on-classpath [] - (filter #(.isDirectory ^File %) - (map #(File. ^String %) - (string/split - (System/getProperty "java.class.path") - (Pattern/compile (Pattern/quote File/pathSeparator)))))) +(defn scan-files + "Scans files to find those which have changed since the last time + 'scan-files' was run; updates the dependency tracker with + new/changed/deleted files. -(defn scan + files is the collection of files to scan. + + Optional third argument is map of options: + + :platform Either clj (default) or cljs, both defined in + clojure.tools.namespace.find, controls reader options for + parsing files. + + :add-all? If true, assumes all extant files are modified regardless + of filesystem timestamps." + {:added "0.3.0"} + ([tracker files] (scan-files tracker files nil)) + ([tracker files {:keys [platform add-all?]}] + (let [deleted (seq (deleted-files tracker files)) + modified (if add-all? + files + (seq (modified-files tracker files)))] + (if (or deleted modified) + (update-files tracker deleted modified platform) + tracker)))) + +(defn scan-dirs "Scans directories for files which have changed since the last time - 'scan' was run; update the dependency tracker with - new/changed/deleted files. + 'scan-dirs' or 'scan-files' was run; updates the dependency tracker + with new/changed/deleted files. + + dirs is the collection of directories to scan, defaults to all + directories on Clojure's classpath. + + Optional third argument is map of options: + + :platform Either clj (default) or cljs, both defined in + clojure.tools.namespace.find, controls file extensions + and reader options. + + :add-all? If true, assumes all extant files are modified regardless + of filesystem timestamps." + {:added "0.3.0"} + ([tracker] (scan-dirs tracker nil nil)) + ([tracker dirs] (scan-dirs tracker dirs nil)) + ([tracker dirs {:keys [platform add-all?] :as options}] + (let [ds (or (seq dirs) (classpath-directories))] + (scan-files tracker (find-files ds platform) options)))) + +(defn scan + "DEPRECATED: replaced by scan-dirs. + + Scans directories for Clojure (.clj, .cljc) source 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." + {:added "0.2.0" + :deprecated "0.3.0"} [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))) + (scan-dirs tracker dirs {:platform find/clj})) (defn scan-all - "Scans directories for all Clojure source files and updates the + "DEPRECATED: replaced by scan-dirs. + + Scans directories for all Clojure source files and updates the dependency tracker to reload files. If no dirs given, defaults to all directories on the classpath." + {:added "0.2.0" + :deprecated "0.3.0"} [tracker & dirs] - (let [ds (or (seq dirs) (dirs-on-classpath)) - files (find-files ds) - deleted (seq (deleted-files tracker files))] - (update-files tracker deleted files))) + (scan-dirs tracker dirs {:platform find/clj :add-all? true})) diff --git a/src/main/clojure/clojure/tools/namespace/file.clj b/src/main/clojure/clojure/tools/namespace/file.clj index 2d9c859..da76e97 100644 --- a/src/main/clojure/clojure/tools/namespace/file.clj +++ b/src/main/clojure/clojure/tools/namespace/file.clj @@ -16,26 +16,52 @@ (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))) + unevaluated form. Returns nil if read fails due to invalid syntax or + if a ns declaration cannot be found. read-opts is passed through to + tools.reader/read." + ([file] + (read-file-ns-decl file nil)) + ([file read-opts] + (with-open [rdr (PushbackReader. (io/reader file))] + (parse/read-ns-decl rdr read-opts)))) + +(defn file-with-extension? + "Returns true if the java.io.File represents a file whose name ends + with one of the Strings in extensions." + {:added "0.3.0"} + [^java.io.File file extensions] + (and (.isFile file) + (let [name (.getName file)] + (some #(.endsWith name %) extensions)))) + +(def ^{:added "0.3.0"} + clojure-extensions + "File extensions for Clojure (JVM) files." + (list ".clj" ".cljc")) + +(def ^{:added "0.3.0"} + clojurescript-extensions + "File extensions for ClojureScript files." + (list ".cljs" ".cljc")) (defn clojure-file? - "Returns true if the java.io.File represents a normal Clojure source - file." + "Returns true if the java.io.File represents a file which will be + read by the Clojure (JVM) compiler." [^java.io.File file] - (and (.isFile file) - (or - (.endsWith (.getName file) ".clj") - (.endsWith (.getName file) ".cljc")))) + (file-with-extension? file clojure-extensions)) + +(defn clojurescript-file? + "Returns true if the java.io.File represents a file which will be + read by the ClojureScript compiler." + {:added "0.3.0"} + [^java.io.File file] + (file-with-extension? file clojurescript-extensions)) ;;; Dependency tracker -(defn- files-and-deps [files] +(defn- files-and-deps [files read-opts] (reduce (fn [m file] - (if-let [decl (read-file-ns-decl file)] + (if-let [decl (read-file-ns-decl file read-opts)] (let [deps (parse/deps-from-ns-decl decl) name (second decl)] (-> m @@ -48,12 +74,15 @@ (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] merge-map filemap)))) + tracker with those files added. read-opts is passed through to + tools.reader." + ([tracker files] + (add-files tracker files nil)) + ([tracker files read-opts] + (let [{:keys [depmap filemap]} (files-and-deps files read-opts)] + (-> tracker + (track/add depmap) + (update-in [::filemap] merge-map filemap))))) (defn remove-files "Returns an updated dependency tracker with files removed. The files diff --git a/src/main/clojure/clojure/tools/namespace/find.clj b/src/main/clojure/clojure/tools/namespace/find.clj index e22abbc..9ced9e5 100644 --- a/src/main/clojure/clojure/tools/namespace/find.clj +++ b/src/main/clojure/clojure/tools/namespace/find.clj @@ -10,7 +10,8 @@ ^{:author "Stuart Sierra", :doc "Search for namespace declarations in directories and JAR files."} clojure.tools.namespace.find - (:require [clojure.java.io :as io] + (:require [clojure.java.classpath :as classpath] + [clojure.java.io :as io] [clojure.set :as set] [clojure.tools.namespace.file :as file] [clojure.tools.namespace.parse :as parse]) @@ -18,104 +19,173 @@ InputStreamReader) (java.util.jar JarFile JarEntry))) -;;; JAR-file utilities, adapted from clojure.java.classpath +(def ^{:added "0.3.0"} + clj + "Platform definition of file extensions and reader options for + Clojure (.clj and .cljc) source files." + {:read-opts parse/clj-read-opts + :extensions file/clojure-extensions}) -(defn- jar-file? - "Returns true if file is a normal file with a .jar or .JAR extension." - [f] - (let [file (io/file f)] - (and (.isFile file) - (or (.endsWith (.getName file) ".jar") - (.endsWith (.getName file) ".JAR"))))) - -(defn- jar-files - "Given a sequence of File objects, filters it for JAR files, returns - a sequence of java.util.jar.JarFile objects." - [files] - (map #(JarFile. ^File %) (filter jar-file? files))) - -(defn- filenames-in-jar - "Returns a sequence of Strings naming the non-directory entries in - the JAR file." - [^JarFile jar-file] - (map #(.getName ^JarEntry %) - (filter #(not (.isDirectory ^JarEntry %)) - (enumeration-seq (.entries jar-file))))) +(def ^{:added "0.3.0"} + cljs + "Platform definition of file extensions and reader options for + ClojureScript (.cljs and .cljc) source files." + {:read-opts parse/cljs-read-opts + :extensions file/clojurescript-extensions}) ;;; Finding namespaces in a directory tree +(defn- sort-files-breadth-first + [files] + (sort-by #(.getAbsolutePath ^File %) files)) + +(defn find-sources-in-dir + "Searches recursively under dir for source files. Returns a sequence + of File objects, in breadth-first sort order. + + Optional second argument is either clj (default) or cljs, both + defined in clojure.tools.namespace.find." + {:added "0.3.0"} + ([dir] + (find-sources-in-dir dir nil)) + ([^File dir platform] + (let [{:keys [extensions]} (or platform clj)] + (->> (file-seq dir) + (filter #(file/file-with-extension? % extensions)) + sort-files-breadth-first)))) + (defn find-clojure-sources-in-dir - "Searches recursively under dir for Clojure source files (.clj, .cljc). + "DEPRECATED: replaced by find-sources-in-dir + + Searches recursively under dir for Clojure source files (.clj, .cljc). Returns a sequence of File objects, in breadth-first sort order." + {:added "0.2.0" + :deprecated "0.3.0"} [^File dir] - ;; Use sort by absolute path to get breadth-first search. - (sort-by #(.getAbsolutePath ^File %) - (filter file/clojure-file? (file-seq dir)))) + (find-sources-in-dir dir clj)) (defn find-ns-decls-in-dir "Searches dir recursively for (ns ...) declarations in Clojure - source files; returns the unevaluated ns declarations." - [^File dir] - (keep file/read-file-ns-decl (find-clojure-sources-in-dir dir))) + source files; returns the unevaluated ns declarations. + + Optional second argument platform is either clj (default) or cljs, + both defined in clojure.tools.namespace.find." + {:added "0.2.0"} + ([dir] (find-ns-decls-in-dir dir nil)) + ([dir platform] + (keep #(file/read-file-ns-decl % (:read-opts platform)) + (find-sources-in-dir dir platform)))) (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))) + source files; returns the symbol names of the declared namespaces. + + Optional second argument platform is either clj (default) or cljs, + both defined in clojure.tools.namespace.find." + {:added "0.3.0"} + ([dir] (find-namespaces-in-dir dir nil)) + ([dir platform] + (map second (find-ns-decls-in-dir dir platform)))) ;;; Finding namespaces in JAR files +(defn- ends-with-extension + [^String filename extensions] + (some #(.endsWith filename %) extensions)) + +(defn sources-in-jar + "Returns a sequence of source file names found in the JAR file. + + Optional second argument platform is either clj (default) or cljs, + both defined in clojure.tools.namespace.find." + {:added "0.3.0"} + ([jar-file] + (sources-in-jar jar-file nil)) + ([^JarFile jar-file platform] + (let [{:keys [extensions]} (or platform clj)] + (filter #(ends-with-extension % extensions) + (classpath/filenames-in-jar jar-file))))) + (defn clojure-sources-in-jar - "Returns a sequence of filenames ending in .clj or .cljc found in the JAR file." - [^JarFile jar-file] - (filter #(or (.endsWith ^String % ".clj") (.endsWith ^String % ".cljc")) - (filenames-in-jar jar-file))) + "DEPRECATED: replaced by sources-in-jar + + Returns a sequence of filenames ending in .clj or .cljc found in the + JAR file." + {:added "0.2.0" + :deprecated "0.3.0"} + [jar-file] + (sources-in-jar jar-file clj)) (defn read-ns-decl-from-jarfile-entry "Attempts to read a (ns ...) declaration from the named entry in the - JAR file, and returns the unevaluated form. Returns nil if the read - fails, or if the first form is not a ns declaration." - [^JarFile jarfile ^String entry-name] - (with-open [rdr (PushbackReader. - (io/reader - (.getInputStream jarfile (.getEntry jarfile entry-name))))] - (parse/read-ns-decl rdr))) + JAR file, and returns the unevaluated form. + + Optional third argument platform is either clj (default) or cljs, + both defined in clojure.tools.namespace.find." + ([jarfile entry-name] + (read-ns-decl-from-jarfile-entry jarfile entry-name nil)) + ([^JarFile jarfile ^String entry-name platform] + (let [{:keys [read-opts]} (or platform clj)] + (with-open [rdr (PushbackReader. + (io/reader + (.getInputStream jarfile (.getEntry jarfile entry-name))))] + (parse/read-ns-decl rdr read-opts))))) (defn find-ns-decls-in-jarfile - "Searches the JAR file for Clojure source files containing (ns ...) - declarations; returns the unevaluated ns declarations." - [^JarFile jarfile] - (filter identity - (map #(read-ns-decl-from-jarfile-entry jarfile %) - (clojure-sources-in-jar jarfile)))) + "Searches the JAR file for source files containing (ns ...) + declarations; returns the unevaluated ns declarations. + + Optional second argument platform is either clj (default) or cljs, + both defined in clojure.tools.namespace.find." + ([jarfile] + (find-ns-decls-in-jarfile jarfile nil)) + ([^JarFile jarfile platform] + (keep #(read-ns-decl-from-jarfile-entry jarfile % platform) + (sources-in-jar jarfile platform)))) (defn find-namespaces-in-jarfile - "Searches the JAR file for Clojure source files containing (ns ...) + "Searches the JAR file for platform source files containing (ns ...) declarations. Returns a sequence of the symbol names of the - declared namespaces." - [^JarFile jarfile] - (map second (find-ns-decls-in-jarfile jarfile))) + declared namespaces. + + Optional second argument platform is either clj (default) or cljs, + both defined in clojure.tools.namespace.find." + ([jarfile] + (find-namespaces-in-jarfile jarfile nil)) + ([^JarFile jarfile platform] + (map second (find-ns-decls-in-jarfile jarfile platform)))) ;;; Finding namespaces (defn find-ns-decls "Searches a sequence of java.io.File objects (both directories and - JAR files) for .clj or .cljc source files containing (ns...) declarations. - Returns a sequence of the unevaluated ns declaration forms. Use with - clojure.java.classpath to search Clojure's classpath." - [files] - (concat - (mapcat find-ns-decls-in-dir (filter #(.isDirectory ^File %) files)) - (mapcat find-ns-decls-in-jarfile (jar-files files)))) + JAR files) for platform source files containing (ns...) + declarations. Returns a sequence of the unevaluated ns declaration + forms. Use with clojure.java.classpath to search Clojure's + classpath. + + Optional second argument platform is either clj (default) or cljs, + both defined in clojure.tools.namespace.find." + ([files] + (find-ns-decls files nil)) + ([files platform] + (concat + (mapcat #(find-ns-decls-in-dir % platform) + (filter #(.isDirectory ^File %) files)) + (mapcat #(find-ns-decls-in-jarfile % platform) + (map #(JarFile. %) (filter classpath/jar-file? files)))))) (defn find-namespaces "Searches a sequence of java.io.File objects (both directories and - JAR files) for .clj or .cljc source files containing (ns...) declarations. - Returns a sequence of the symbol names of the declared + JAR files) for platform source files containing (ns...) + declarations. Returns a sequence of the symbol names of the declared namespaces. Use with clojure.java.classpath to search Clojure's - classpath." - [files] - (map second (find-ns-decls files))) - + classpath. + + Optional second argument platform is either clj (default) or cljs, + both defined in clojure.tools.namespace.find." + ([files] + (find-namespaces files nil)) + ([files platform] + (map second (find-ns-decls files platform)))) diff --git a/src/main/clojure/clojure/tools/namespace/parse.clj b/src/main/clojure/clojure/tools/namespace/parse.cljc similarity index 60% rename from src/main/clojure/clojure/tools/namespace/parse.clj rename to src/main/clojure/clojure/tools/namespace/parse.cljc index 78a8fc0..cba124e 100644 --- a/src/main/clojure/clojure/tools/namespace/parse.clj +++ b/src/main/clojure/clojure/tools/namespace/parse.cljc @@ -10,24 +10,9 @@ :doc "Parse Clojure namespace (ns) declarations and extract dependencies."} clojure.tools.namespace.parse - (:require [clojure.set :as set])) - -(defn- read-clj - "Calls clojure.core/read. If reader conditionals are - supported (Clojure 1.7) then adds options {:read-cond :allow}." - [rdr] - (if (resolve 'clojure.core/reader-conditional?) - (read {:read-cond :allow} rdr) - (read rdr))) - -(defn- force-errors - "Forces reader errors to be thrown immediately. Some versions of - Clojure accept invalid forms in the reader and only throw an - exception when they are printed. - See http://dev.clojure.org/jira/browse/TNS-1" - [form] - (str form) ; str forces errors - form) + (:require #?(:clj [clojure.tools.reader :as reader] + :cljs [cljs.tools.reader :as reader]) + [clojure.set :as set])) (defn comment? "Returns true if form is a (comment ...)" @@ -39,22 +24,33 @@ [form] (and (list? form) (= 'ns (first form)))) +(def clj-read-opts + {:read-cond :allow + :features #{:clj}}) + +(def cljs-read-opts + {:read-cond :allow + :features #{:cljs}}) + (defn read-ns-decl - "Attempts to read a (ns ...) declaration from a - java.io.PushbackReader, and returns the unevaluated form. Returns - the first top-level ns form found. Returns nil if read fails or if a - ns declaration cannot be found. Note that read can execute code - (controlled by *read-eval*), and as such should be used only with - trusted sources." - [rdr] - {:pre [(instance? java.io.PushbackReader rdr)]} - (try - (loop [] - (let [form (force-errors (read-clj rdr))] - (if (ns-decl? form) - form - (recur)))) - (catch Exception e nil))) + "Attempts to read a (ns ...) declaration from a reader, and returns + the unevaluated form. Returns the first top-level ns form found. + Returns nil if a ns declaration cannot be found. Note that read can + execute code (controlled by tools.reader/*read-eval*), and as such + should be used only with trusted sources. read-opts is passed + through to tools.reader/read, defaults to allow conditional reader + expressions with :features #{:clj}" + ([rdr] + (read-ns-decl rdr nil)) + ([rdr read-opts] + (let [opts (assoc (or read-opts clj-read-opts) + :eof ::eof)] + (loop [] + (let [form (reader/read opts rdr)] + (cond + (ns-decl? form) form + (= ::eof form) nil + :else (recur))))))) ;;; Parsing dependencies @@ -91,12 +87,25 @@ (keyword? form) ; Some people write (:require ... :reload-all) nil :else - (throw (IllegalArgumentException. - (pr-str "Unparsable namespace form:" form))))) + (throw (ex-info "Unparsable namespace form" + {:reason ::unparsable-ns-form + :form form})))) + +(def ^:private ns-clause-head-names + "Set of symbol/keyword names which can appear as the head of a + clause in the ns form." + #{"use" "require" "use-macros" "require-macros"}) + +(def ^:private ns-clause-heads + "Set of all symbols and keywords which can appear at the head of a + dependency clause in the ns form." + (set (mapcat (fn [name] (list (keyword name) + (symbol name))) + ns-clause-head-names))) (defn- deps-from-ns-form [form] (when (and (sequential? form) ; should be list but sometimes is not - (contains? #{:use :require 'use 'require} (first form))) + (contains? ns-clause-heads (first form))) (mapcat #(deps-from-libspec nil %) (rest form)))) (defn deps-from-ns-decl diff --git a/src/main/clojure/clojure/tools/namespace/repl.clj b/src/main/clojure/clojure/tools/namespace/repl.clj index 5c3dc7b..d75a805 100644 --- a/src/main/clojure/clojure/tools/namespace/repl.clj +++ b/src/main/clojure/clojure/tools/namespace/repl.clj @@ -11,11 +11,12 @@ clojure.tools.namespace.repl (:require [clojure.tools.namespace.track :as track] [clojure.tools.namespace.dir :as dir] + [clojure.tools.namespace.find :as find] [clojure.tools.namespace.reload :as reload])) -(defonce ^:private refresh-tracker (track/tracker)) +(defonce refresh-tracker (track/tracker)) -(defonce ^:private refresh-dirs []) +(defonce refresh-dirs []) (defn- print-and-return [tracker] (if-let [e (::reload/error tracker)] @@ -79,7 +80,7 @@ (when (find-ns ns-name) (alias alias-sym ns-name)))) -(defn- do-refresh [scan-fn after-sym] +(defn- do-refresh [scan-opts after-sym] (when after-sym (assert (symbol? after-sym) ":after value must be a symbol") (assert (namespace after-sym) @@ -87,8 +88,7 @@ (let [current-ns-name (ns-name *ns*) current-ns-refers (referred *ns*) current-ns-aliases (aliased *ns*)] - (alter-var-root #'refresh-tracker - #(apply scan-fn % refresh-dirs)) + (alter-var-root #'refresh-tracker dir/scan-dirs refresh-dirs scan-opts) (alter-var-root #'refresh-tracker remove-disabled) (print-pending-reloads refresh-tracker) (alter-var-root #'refresh-tracker reload/track-reload) @@ -142,7 +142,7 @@ been reloaded." [& options] (let [{:keys [after]} options] - (do-refresh dir/scan after))) + (do-refresh {:platform find/clj} after))) (defn refresh-all "Scans source code directories for all Clojure source files and @@ -159,7 +159,7 @@ been reloaded." [& options] (let [{:keys [after]} options] - (do-refresh dir/scan-all after))) + (do-refresh {:platform find/clj :add-all? true} after))) (defn set-refresh-dirs "Sets the directories which are scanned by 'refresh'. Supports the diff --git a/src/main/clojure/clojure/tools/namespace/track.clj b/src/main/clojure/clojure/tools/namespace/track.cljc similarity index 100% rename from src/main/clojure/clojure/tools/namespace/track.clj rename to src/main/clojure/clojure/tools/namespace/track.cljc