|
| 1 | +(ns boot.from.bultitude.core |
| 2 | + {:boot/from :Raynes/bultitude} |
| 3 | + (:require [clojure.java.io :as io] |
| 4 | + [clojure.string :as string] |
| 5 | + [dynapath.util :as dp]) |
| 6 | + (:import (java.util.jar JarFile JarEntry) |
| 7 | + (java.util.zip ZipException) |
| 8 | + (java.io File BufferedReader PushbackReader InputStreamReader) |
| 9 | + (clojure.lang DynamicClassLoader))) |
| 10 | + |
| 11 | +(declare namespace-forms-in-dir |
| 12 | + file->namespace-forms) |
| 13 | + |
| 14 | +(defn- clj? [^File f] |
| 15 | + (and (not (.isDirectory f)) |
| 16 | + (.endsWith (.getName f) ".clj"))) |
| 17 | + |
| 18 | +(defn- clj-jar-entry? [^JarEntry f] |
| 19 | + (and (not (.isDirectory f)) |
| 20 | + (.endsWith (.getName f) ".clj"))) |
| 21 | + |
| 22 | +(defn- jar? [^File f] |
| 23 | + (and (.isFile f) (.endsWith (.getName f) ".jar"))) |
| 24 | + |
| 25 | +(defn- read-ns-form |
| 26 | + "Given a reader on a Clojure source file, read until an ns form is found." |
| 27 | + ([rdr] (read-ns-form rdr true)) |
| 28 | + ([rdr ignore-unreadable?] |
| 29 | + (let [form (try (read rdr false ::done) |
| 30 | + (catch Exception e |
| 31 | + (if ignore-unreadable? |
| 32 | + ::done |
| 33 | + (throw e))))] |
| 34 | + (if (and (list? form) (= 'ns (first form))) |
| 35 | + form |
| 36 | + (when-not (= ::done form) |
| 37 | + (recur rdr ignore-unreadable?)))))) |
| 38 | + |
| 39 | +(defn ns-form-for-file |
| 40 | + ([file] (ns-form-for-file file true)) |
| 41 | + ([file ignore-unreadable?] |
| 42 | + (with-open [r (PushbackReader. (io/reader file))] |
| 43 | + (read-ns-form r ignore-unreadable?)))) |
| 44 | + |
| 45 | +(defn namespaces-in-dir |
| 46 | + "Return a seq of all namespaces found in Clojure source files in dir." |
| 47 | + ([dir] (namespaces-in-dir dir true)) |
| 48 | + ([dir ignore-unreadable?] |
| 49 | + (map second (namespace-forms-in-dir dir ignore-unreadable?)))) |
| 50 | + |
| 51 | +(defn namespace-forms-in-dir |
| 52 | + "Return a seq of all namespace forms found in Clojure source files in dir." |
| 53 | + ([dir] (namespace-forms-in-dir dir true)) |
| 54 | + ([dir ignore-unreadable?] |
| 55 | + (for [^File f (file-seq (io/file dir)) |
| 56 | + :when (and (clj? f) (.canRead f)) |
| 57 | + :let [ns-form (ns-form-for-file f ignore-unreadable?)] |
| 58 | + :when ns-form] |
| 59 | + ns-form))) |
| 60 | + |
| 61 | +(defn- ns-form-in-jar-entry |
| 62 | + ([jarfile entry] (ns-form-in-jar-entry jarfile entry true)) |
| 63 | + ([^JarFile jarfile ^JarEntry entry ignore-unreadable?] |
| 64 | + (with-open [rdr (-> jarfile |
| 65 | + (.getInputStream entry) |
| 66 | + InputStreamReader. |
| 67 | + BufferedReader. |
| 68 | + PushbackReader.)] |
| 69 | + (read-ns-form rdr ignore-unreadable?)))) |
| 70 | + |
| 71 | +(defn- namespace-forms-in-jar |
| 72 | + ([jar] (namespace-forms-in-jar jar true)) |
| 73 | + ([^File jar ignore-unreadable?] |
| 74 | + (try |
| 75 | + (let [jarfile (JarFile. jar)] |
| 76 | + (for [entry (enumeration-seq (.entries jarfile)) |
| 77 | + :when (clj-jar-entry? entry) |
| 78 | + :let [ns-form (ns-form-in-jar-entry jarfile entry |
| 79 | + ignore-unreadable?)] |
| 80 | + :when ns-form] |
| 81 | + ns-form)) |
| 82 | + (catch ZipException e |
| 83 | + (throw (Exception. (str "jar file corrupt: " jar) e)))))) |
| 84 | + |
| 85 | +(defn- split-classpath [^String classpath] |
| 86 | + (.split classpath (System/getProperty "path.separator"))) |
| 87 | + |
| 88 | +(defn loader-classpath |
| 89 | + "Returns a sequence of File objects from a classloader." |
| 90 | + [loader] |
| 91 | + (map io/as-file (dp/classpath-urls loader))) |
| 92 | + |
| 93 | +(defn classpath-files |
| 94 | + "Returns a sequence of File objects of the elements on the classpath." |
| 95 | + ([classloader] |
| 96 | + (map io/as-file (dp/all-classpath-urls classloader))) |
| 97 | + ([] (classpath-files (clojure.lang.RT/baseLoader)))) |
| 98 | + |
| 99 | +(defn- classpath->collection [classpath] |
| 100 | + (if (coll? classpath) |
| 101 | + classpath |
| 102 | + (split-classpath classpath))) |
| 103 | + |
| 104 | +(defn- classpath->files [classpath] |
| 105 | + (map io/file classpath)) |
| 106 | + |
| 107 | +(defn file->namespaces |
| 108 | + "Map a classpath file to the namespaces it contains. `prefix` allows for |
| 109 | + reducing the namespace search space. For large directories on the classpath, |
| 110 | + passing a `prefix` can provide significant efficiency gains." |
| 111 | + [^String prefix ^File f] |
| 112 | + (map second (file->namespace-forms prefix f))) |
| 113 | + |
| 114 | +(defn file->namespace-forms |
| 115 | + "Map a classpath file to the namespace forms it contains. `prefix` allows for |
| 116 | + reducing the namespace search space. For large directories on the classpath, |
| 117 | + passing a `prefix` can provide significant efficiency gains." |
| 118 | + ([prefix f] (file->namespace-forms prefix f true)) |
| 119 | + ([^String prefix ^File f ignore-unreadable?] |
| 120 | + (cond |
| 121 | + (.isDirectory f) (namespace-forms-in-dir |
| 122 | + (if prefix |
| 123 | + (io/file f (-> prefix |
| 124 | + (.replaceAll "\\." "/") |
| 125 | + (.replaceAll "-" "_"))) |
| 126 | + f) ignore-unreadable?) |
| 127 | + (jar? f) (let [ns-list (namespace-forms-in-jar f ignore-unreadable?)] |
| 128 | + (if prefix |
| 129 | + (for [nspace ns-list |
| 130 | + :let [sym (second nspace)] |
| 131 | + :when (and sym (.startsWith (name sym) prefix))] |
| 132 | + nspace) |
| 133 | + ns-list))))) |
| 134 | + |
| 135 | +(defn namespace-forms-on-classpath |
| 136 | + "Returs the namespaces forms matching the given prefix both on disk and |
| 137 | + inside jar files. If :prefix is passed, only return namespaces that begin with |
| 138 | + this prefix. If :classpath is passed, it should be a seq of File objects or a |
| 139 | + classpath string. If it is not passed, default to java.class.path and the |
| 140 | + current classloader, assuming it is a dynamic classloader." |
| 141 | + [& {:keys [prefix classpath ignore-unreadable?] |
| 142 | + :or {classpath (classpath-files) ignore-unreadable? true}}] |
| 143 | + (mapcat |
| 144 | + #(file->namespace-forms prefix % ignore-unreadable?) |
| 145 | + (->> classpath |
| 146 | + classpath->collection |
| 147 | + classpath->files))) |
| 148 | + |
| 149 | +(defn namespaces-on-classpath |
| 150 | + "Return symbols of all namespaces matching the given prefix both on disk and |
| 151 | + inside jar files. If :prefix is passed, only return namespaces that begin with |
| 152 | + this prefix. If :classpath is passed, it should be a seq of File objects or a |
| 153 | + classpath string. If it is not passed, default to java.class.path and the |
| 154 | + current classloader, assuming it is a dynamic classloader." |
| 155 | + [& args] |
| 156 | + (map second (apply namespace-forms-on-classpath args))) |
| 157 | + |
| 158 | +(defn path-for |
| 159 | + "Transform a namespace into a .clj file path relative to classpath root." |
| 160 | + [namespace] |
| 161 | + (str (-> (str namespace) |
| 162 | + (.replace \- \_) |
| 163 | + (.replace \. \/)) |
| 164 | + ".clj")) |
| 165 | + |
| 166 | +(defn doc-from-ns-form |
| 167 | + "Extract the docstring from a given ns form without evaluating the form. The docstring returned should be the return value of (:doc (meta namespace-symbol)) if the ns-form were to be evaluated." |
| 168 | + [ns-form] |
| 169 | + (:doc (meta (second (second (second (macroexpand ns-form))))))) |
0 commit comments