|
7 | 7 | ; You must not remove this notice, or any other, from this software. |
8 | 8 |
|
9 | 9 | (ns clojure.tools.deps.edn |
| 10 | + "Functions for reading, validating, and manipulating deps.edn |
| 11 | + files and data structures." |
10 | 12 | (:require |
11 | 13 | [clojure.edn :as edn] |
12 | 14 | [clojure.java.io :as jio] |
13 | 15 | [clojure.string :as str] |
14 | 16 | [clojure.tools.deps.specs :as specs] |
| 17 | + [clojure.tools.deps.util.dir :as dir] |
15 | 18 | [clojure.walk :as walk]) |
16 | 19 | (:import |
17 | | - [java.io File PushbackReader] |
| 20 | + [java.io BufferedReader File InputStreamReader PushbackReader Reader] |
18 | 21 | [clojure.lang EdnReader$ReaderException] |
19 | 22 | )) |
20 | 23 |
|
|
40 | 43 | (let [abs-path (.getAbsolutePath (jio/file path))] |
41 | 44 | (ex-info (format fmt abs-path) {:path abs-path}))) |
42 | 45 |
|
43 | | -(defn read-edn |
44 | | - "Read edn from file f, which should contain exactly one edn value. |
45 | | - If f exists but is blank, nil is returned. |
46 | | - Throws if file is unreadable or contains multiple values. |
| 46 | +(defn- read-edn |
| 47 | + "Read edn from Reader r, which should contain exactly one edn value. |
| 48 | + If source exists but is blank, nil is returned. |
| 49 | + Throws if source is unreadable or contains multiple values. |
| 50 | +
|
47 | 51 | Opts: |
48 | | - :path String path to file being read" |
49 | | - [^File f & opts] |
50 | | - (with-open [rdr (PushbackReader. (jio/reader f))] |
51 | | - (let [EOF (Object.) |
52 | | - val (try |
53 | | - (let [val (edn/read {:default tagged-literal :eof EOF} rdr)] |
54 | | - (if (identical? EOF val) |
55 | | - nil ;; empty file |
56 | | - (if (not (identical? EOF (edn/read {:eof EOF} rdr))) |
57 | | - (throw (ex-info "Expected edn to contain a single value." {})) |
58 | | - val))) |
59 | | - (catch EdnReader$ReaderException e |
60 | | - (throw (io-err (str (.getMessage e) " (%s)") opts))) |
61 | | - (catch RuntimeException t |
62 | | - (if (str/starts-with? (.getMessage t) "EOF while reading") |
63 | | - (throw (io-err "Error reading edn, delimiter unmatched (%s)" opts)) |
64 | | - (throw (io-err (str "Error reading edn. " (.getMessage t) " (%s)") opts)))))]))) |
| 52 | + :path String path to file being read, for error reporting" |
| 53 | + [^Reader r & opts] |
| 54 | + (with-open [rdr (PushbackReader. r)] |
| 55 | + (let [EOF (Object.)] |
| 56 | + (try |
| 57 | + (let [val (edn/read {:default tagged-literal :eof EOF} rdr)] |
| 58 | + (if (identical? EOF val) |
| 59 | + nil ;; empty file |
| 60 | + (if (not (identical? EOF (edn/read {:eof EOF} rdr))) |
| 61 | + (throw (ex-info "Expected edn to contain a single value." {})) |
| 62 | + val))) |
| 63 | + (catch EdnReader$ReaderException e |
| 64 | + (throw (io-err (str (.getMessage e) " (%s)") opts))) |
| 65 | + (catch RuntimeException t |
| 66 | + (if (str/starts-with? (.getMessage t) "EOF while reading") |
| 67 | + (throw (io-err "Error reading edn, delimiter unmatched (%s)" opts)) |
| 68 | + (throw (io-err (str "Error reading edn. " (.getMessage t) " (%s)") opts)))))))) |
65 | 69 |
|
66 | 70 | (defn validate |
67 | 71 | "Validate a deps-edn map according to the specs, throw if invalid. |
| 72 | + Returns the deps-edn map. |
| 73 | +
|
68 | 74 | Opts: |
69 | 75 | :path String path to file being read" |
70 | 76 | [deps-edn & opts] |
|
79 | 85 | (if (simple-symbol? s) |
80 | 86 | (let [cs (as-> (name s) n (symbol n n))] |
81 | 87 | (printerrln "DEPRECATED: Libs must be qualified, change" s "=>" cs |
82 | | - (if-let [path (:path opts)] (str "(" path ")" ""))) |
| 88 | + (if-let [path (:path opts)] (str "(" path ")") "")) |
83 | 89 | cs) |
84 | 90 | s)) |
85 | 91 |
|
|
100 | 106 |
|
101 | 107 | (defn canonicalize |
102 | 108 | "Canonicalize a deps.edn map (convert simple lib symbols to qualified lib symbols). |
| 109 | + Returns the deps-edn map. |
| 110 | +
|
103 | 111 | Opts: |
104 | 112 | :path String path to file being read" |
105 | 113 | [deps-edn & opts] |
|
119 | 127 | (defn read-deps |
120 | 128 | "Corece f to a file with jio/file, then read, validate, and canonicalize |
121 | 129 | the deps.edn. This is the primary entry point for reading. |
| 130 | + Use read-edn, validate, or canonicalize for individual steps. |
122 | 131 |
|
123 | | - Opts: none" |
| 132 | + Opts: none for now" |
124 | 133 | [f & opts] |
125 | 134 | (let [file (jio/file f) |
126 | 135 | opts {:path (.getPath file)}] |
127 | 136 | (when (.exists file) |
128 | | - (-> file (read-edn opts) (validate opts) (canonicalize opts))))) |
| 137 | + (-> file jio/reader (read-edn opts) (validate opts) (canonicalize opts))))) |
| 138 | + |
| 139 | +;;;; Dep chain lookups |
| 140 | + |
| 141 | +(defn root-deps |
| 142 | + "Read the root deps.edn resource from the classpath at the path |
| 143 | + clojure/tools/deps/deps.edn" |
| 144 | + [] |
| 145 | + (let [url (jio/resource "clojure/tools/deps/deps.edn")] |
| 146 | + ;; TODO: separate file reading and resource reading |
| 147 | + (read-edn (BufferedReader. (InputStreamReader. (.openStream url)))))) |
| 148 | + |
| 149 | +(defn user-deps-path |
| 150 | + "Use the same logic as clj to calculate the location of the user deps.edn. |
| 151 | + Note that it's possible no file may exist at this location." |
| 152 | + [] |
| 153 | + (let [config-env (System/getenv "CLJ_CONFIG") |
| 154 | + xdg-env (System/getenv "XDG_CONFIG_HOME") |
| 155 | + home (System/getProperty "user.home") |
| 156 | + config-dir (cond config-env config-env |
| 157 | + xdg-env (str xdg-env File/separator "clojure") |
| 158 | + :else (str home File/separator ".clojure"))] |
| 159 | + (str config-dir File/separator "deps.edn"))) |
| 160 | + |
| 161 | +(defn user-deps |
| 162 | + "Calculate the user deps.edn per user-deps-path, read and |
| 163 | + return the deps.edn data" |
| 164 | + [] |
| 165 | + (-> (user-deps-path) jio/file dir/canonicalize read-deps)) |
| 166 | + |
| 167 | +(defn project-deps-path |
| 168 | + "Calculate the project deps.edn location. This |
| 169 | + is the deps.edn in the current directory, as defined by |
| 170 | + clojure.tools.deps.util.dir/*the-dir* - use with-dir |
| 171 | + to push a new local directory context around a call to |
| 172 | + project-deps-path." |
| 173 | + [] |
| 174 | + (str dir/*the-dir* File/separator "deps.edn")) |
| 175 | + |
| 176 | +(defn project-deps |
| 177 | + "Calculate the project deps.edn location " |
| 178 | + [] |
| 179 | + (-> (project-deps-path) jio/file dir/canonicalize read-deps)) |
| 180 | + |
| 181 | +(defn- choose-deps |
| 182 | + [requested standard-fn] |
| 183 | + (cond |
| 184 | + (= :standard requested) (standard-fn) |
| 185 | + (string? requested) (-> requested jio/file dir/canonicalize read-deps) |
| 186 | + (or (nil? requested) (map? requested)) requested |
| 187 | + :else (throw (ex-info (format "Unexpected dep source: %s" (pr-str requested)) |
| 188 | + {:requested requested})))) |
| 189 | + |
| 190 | +(defn create-edn-maps |
| 191 | + "Takes optional map of location sources, keys = :root :user :project :extra |
| 192 | + where each key may be: |
| 193 | + :standard (default) - to get the default source |
| 194 | + string - for file path to source |
| 195 | + nil - to omit |
| 196 | + map - a literal map to use |
| 197 | +
|
| 198 | + Returns a set of deps edn maps with the same keys :root :user :project :extra. |
| 199 | + Keys may be missing if source was nil or file was missing." |
| 200 | + ([] |
| 201 | + (create-edn-maps nil)) |
| 202 | + ([{:keys [root user project extra] :as params |
| 203 | + :or {root :standard, user :standard, project :standard}}] |
| 204 | + (let [root-edn (choose-deps root #(root-deps)) |
| 205 | + user-edn (choose-deps user #(user-deps)) |
| 206 | + project-edn (choose-deps project #(project-deps)) |
| 207 | + extra-edn (choose-deps extra (constantly nil))] |
| 208 | + (cond-> {} |
| 209 | + root-edn (assoc :root root-edn) |
| 210 | + user-edn (assoc :user user-edn) |
| 211 | + project-edn (assoc :project project-edn) |
| 212 | + extra-edn (assoc :extra extra-edn))))) |
129 | 213 |
|
130 | 214 | ;;;; deps edn manipulation |
131 | 215 |
|
|
175 | 259 | merge |
176 | 260 | (fn [_v1 v2] v2)))) |
177 | 261 |
|
178 | | -(defn- merge-alias-maps |
| 262 | +(defn merge-alias-maps |
179 | 263 | "Like merge-with, but using custom per-alias-key merge function" |
180 | 264 | [& ms] |
181 | 265 | (reduce |
|
191 | 275 | (->> alias-kws |
192 | 276 | (map #(get-in edn-map [:aliases %])) |
193 | 277 | (apply merge-alias-maps))) |
194 | | - |
195 | | - |
196 | | - |
197 | | - |
198 | | -(defn- chase-key |
199 | | - "Given an aliases set and a keyword k, return a flattened vector of path |
200 | | - entries for that k, resolving recursively if needed, or nil." |
201 | | - [aliases k] |
202 | | - (let [path-coll (get aliases k)] |
203 | | - (when (seq path-coll) |
204 | | - (into [] (mapcat #(if (string? %) [[% {:path-key k}]] (chase-key aliases %))) path-coll)))) |
205 | | - |
206 | | -(defn- flatten-paths |
207 | | - [{:keys [paths aliases] :as deps-edn-map} {:keys [extra-paths] :as classpath-args}] |
208 | | - (let [aliases' (assoc aliases :paths paths :extra-paths extra-paths)] |
209 | | - (into [] (comp (mapcat #(chase-key aliases' %)) (remove nil?)) [:extra-paths :paths]))) |
210 | | - |
|
0 commit comments