|
8 | 8 |
|
9 | 9 | (ns clojure.tools.deps.edn |
10 | 10 | (:require |
| 11 | + [clojure.edn :as edn] |
11 | 12 | [clojure.java.io :as jio] |
12 | 13 | [clojure.string :as str] |
13 | | - [clojure.tools.deps.util.io :as io] |
14 | 14 | [clojure.tools.deps.specs :as specs] |
15 | 15 | [clojure.walk :as walk]) |
16 | 16 | (:import |
17 | | - [java.io File] |
| 17 | + [java.io File PushbackReader] |
18 | 18 | [clojure.lang EdnReader$ReaderException] |
19 | 19 | )) |
20 | 20 |
|
21 | 21 | (set! *warn-on-reflection* true) |
22 | 22 |
|
23 | | -;;;; deps.edn reading |
| 23 | +;;;; Read |
| 24 | + |
| 25 | +(defonce ^:private nl (System/getProperty "line.separator")) |
| 26 | + |
| 27 | +(defn- printerrln |
| 28 | + "println to *err*" |
| 29 | + [& msgs] |
| 30 | + (binding [*out* *err* |
| 31 | + *print-readably* nil] |
| 32 | + (pr (str (str/join " " msgs) nl)) |
| 33 | + (flush))) |
24 | 34 |
|
25 | 35 | (defn- io-err |
26 | | - ^Throwable [fmt ^File f] |
27 | | - (let [path (.getAbsolutePath f)] |
28 | | - (ex-info (format fmt path) {:path path}))) |
29 | | - |
30 | | -(defn- slurp-edn-map |
31 | | - "Read the file specified by the path-segments, slurp it, and read it as edn." |
32 | | - [^File f] |
33 | | - (let [val (try (io/slurp-edn f) |
34 | | - (catch EdnReader$ReaderException e (throw (io-err (str (.getMessage e) " (%s)") f))) |
35 | | - (catch RuntimeException t |
36 | | - (if (str/starts-with? (.getMessage t) "EOF while reading") |
37 | | - (throw (io-err "Error reading edn, delimiter unmatched (%s)" f)) |
38 | | - (throw (io-err (str "Error reading edn. " (.getMessage t) " (%s)") f)))))] |
39 | | - (if (specs/valid-deps? val) |
40 | | - val |
41 | | - (throw (io-err (str "Error reading deps %s. " (specs/explain-deps val)) f))))) |
42 | | - |
43 | | -;; all this canonicalization is deprecated and will eventually be removed |
| 36 | + "Helper function to construct an ex-info for an exception reading |
| 37 | + file at path with the message format fmt (which should have one |
| 38 | + variable for the path)." |
| 39 | + ^Throwable [fmt & {:keys [path]}] |
| 40 | + (let [abs-path (.getAbsolutePath (jio/file path))] |
| 41 | + (ex-info (format fmt abs-path) {:path abs-path}))) |
| 42 | + |
| 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. |
| 47 | + 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)))))]))) |
| 65 | + |
| 66 | +(defn validate |
| 67 | + "Validate a deps-edn map according to the specs, throw if invalid. |
| 68 | + Opts: |
| 69 | + :path String path to file being read" |
| 70 | + [deps-edn & opts] |
| 71 | + (if (specs/valid-deps? deps-edn) |
| 72 | + deps-edn |
| 73 | + (throw (io-err (str "Error reading deps %s. " (specs/explain-deps deps-edn)) opts)))) |
| 74 | + |
| 75 | +;;;; Canonicalize |
44 | 76 |
|
45 | 77 | (defn- canonicalize-sym |
46 | | - ([s] |
47 | | - (canonicalize-sym s nil)) |
48 | | - ([s file-name] |
49 | | - (if (simple-symbol? s) |
50 | | - (let [cs (as-> (name s) n (symbol n n))] |
51 | | - (io/printerrln "DEPRECATED: Libs must be qualified, change" s "=>" cs |
52 | | - (if file-name (str "(" file-name ")") "")) |
53 | | - cs) |
54 | | - s))) |
| 78 | + [s & opts] |
| 79 | + (if (simple-symbol? s) |
| 80 | + (let [cs (as-> (name s) n (symbol n n))] |
| 81 | + (printerrln "DEPRECATED: Libs must be qualified, change" s "=>" cs |
| 82 | + (if-let [path (:path opts)] (str "(" path ")" ""))) |
| 83 | + cs) |
| 84 | + s)) |
55 | 85 |
|
56 | 86 | (defn- canonicalize-exclusions |
57 | | - [{:keys [exclusions] :as coord} file-name] |
| 87 | + [{:keys [exclusions] :as coord} & opts] |
58 | 88 | (if (seq (filter simple-symbol? exclusions)) |
59 | | - (assoc coord :exclusions (mapv #(canonicalize-sym % file-name) exclusions)) |
| 89 | + (assoc coord :exclusions (mapv #(canonicalize-sym % opts) exclusions)) |
60 | 90 | coord)) |
61 | 91 |
|
62 | 92 | (defn- canonicalize-dep-map |
63 | | - [deps-map file-name] |
| 93 | + [deps-map & opts] |
64 | 94 | (when deps-map |
65 | 95 | (reduce-kv (fn [acc lib coord] |
66 | | - (let [new-lib (if (simple-symbol? lib) (canonicalize-sym lib file-name) lib) |
67 | | - new-coord (canonicalize-exclusions coord file-name)] |
| 96 | + (let [new-lib (if (simple-symbol? lib) (canonicalize-sym lib opts) lib) |
| 97 | + new-coord (canonicalize-exclusions coord opts)] |
68 | 98 | (assoc acc new-lib new-coord))) |
69 | 99 | {} deps-map))) |
70 | 100 |
|
71 | | -(defn- canonicalize-all-syms |
72 | | - ([deps-edn] |
73 | | - (canonicalize-all-syms deps-edn nil)) |
74 | | - ([deps-edn file-name] |
75 | | - (walk/postwalk |
76 | | - (fn [x] |
77 | | - (if (map? x) |
78 | | - (reduce (fn [xr k] |
79 | | - (if-let [xm (get xr k)] |
80 | | - (assoc xr k (canonicalize-dep-map xm file-name)) |
81 | | - xr)) |
82 | | - x #{:deps :default-deps :override-deps :extra-deps :classpath-overrides}) |
83 | | - x)) |
84 | | - deps-edn))) |
85 | | - |
86 | | -(defn slurp-deps |
87 | | - "Read a single deps.edn file from disk and canonicalize symbols, |
88 | | - return a deps map. If the file doesn't exist, returns nil." |
89 | | - [^File dep-file] |
90 | | - (when (.exists dep-file) |
91 | | - (-> dep-file slurp-edn-map (canonicalize-all-syms (.getPath dep-file))))) |
| 101 | +(defn canonicalize |
| 102 | + "Canonicalize a deps.edn map (convert simple lib symbols to qualified lib symbols). |
| 103 | + Opts: |
| 104 | + :path String path to file being read" |
| 105 | + [deps-edn & opts] |
| 106 | + (walk/postwalk |
| 107 | + (fn [x] |
| 108 | + (if (map? x) |
| 109 | + (reduce (fn [xr k] |
| 110 | + (if-let [xm (get xr k)] |
| 111 | + (assoc xr k (canonicalize-dep-map xm opts)) |
| 112 | + xr)) |
| 113 | + x #{:deps :default-deps :override-deps :extra-deps :classpath-overrides}) |
| 114 | + x)) |
| 115 | + deps-edn)) |
| 116 | + |
| 117 | +;;;; Read deps with validation and canonicalization |
| 118 | + |
| 119 | +(defn read-deps |
| 120 | + "Corece f to a file with jio/file, then read, validate, and canonicalize |
| 121 | + the deps.edn. This is the primary entry point for reading. |
| 122 | +
|
| 123 | + Opts: none" |
| 124 | + [f & opts] |
| 125 | + (let [file (jio/file f) |
| 126 | + opts {:path (.getPath file)}] |
| 127 | + (when (.exists file) |
| 128 | + (-> file (read-edn opts) (validate opts) (canonicalize opts))))) |
| 129 | + |
| 130 | +;;;; deps edn manipulation |
92 | 131 |
|
93 | 132 | (defn- merge-or-replace |
94 | 133 | "If maps, merge, otherwise replace" |
|
0 commit comments