-
Notifications
You must be signed in to change notification settings - Fork 1
/
resolve-deps.cljs
executable file
·148 lines (135 loc) · 5.95 KB
/
resolve-deps.cljs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/usr/bin/env nbb
;; Copyright (c) 2024, Viasat, Inc
;; Licensed under EPL 2.0
(ns resolve-deps
(:require [promesa.core :as P]
[clojure.string :as S]
[clojure.walk :refer [keywordize-keys]]
[cljs.pprint :refer [pprint]]
[viasat.deps :refer [resolve-dep-order]]
["neodoc" :as neodoc]
["path" :as path]
["fs/promises" :as fs]))
(def usage "Usage:
resolve-deps [options] <dep-str>...
Options:
-p PATH, --path=PATH One or more paths to dep dirs or files (JSON).
Colon or comma separated.
[default: ./] [env: RESOLVE_DEPS_PATH]
--format=FORMAT Output format (nodes, paths, json)
[default: nodes] [env: RESOLVE_DEPS_FORMAT]")
(defn parse-args [argv]
(-> (neodoc/run usage (clj->js {:smartOptions true
:optionsFirst true
:argv argv}))
js->clj))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn read-stream [stream]
(P/create (fn [resolve reject]
(P/let [chunks (atom [])]
(doto stream
(.on "data" #(swap! chunks conj %))
(.on "error" reject)
(.on "end" #(resolve (apply str @chunks)))
(.resume))))))
(defn load-json
[file]
(P/->> (if (= "-" file)
(read-stream js/process.stdin)
(fs/readFile file "utf8"))
js/JSON.parse
js->clj))
(defn parse-dep-str
"Parse a dependency string of whitespace separated dep strings into
a sequence of deps. Alternation deps are specified as two or more
dependencies delimited by a '|' and are returned as a sequences of
the alternatives. Order only (weak) deps are prefixed with a '+' and
are returned as a map {:after DEP}."
[raw-str]
(let [s (S/replace raw-str #"#[^\n]*" " ")]
(if (empty? s)
[]
(for [dep (S/split s #"[, \n]+")]
(cond
(re-seq #"[|]" dep) {:or (S/split dep #"[|]")}
(re-seq #"^\+" dep) {:after (S/replace dep #"^\+" "")}
:else dep)))))
(defn load-deps-files
"Takes a paths sequence and finds all files matching
path/*/deps-file. Returns a map of dep nodes to node data. Each node
data map contains:
- node: name of the node
- path: path to dep directory
- dep-str: raw dependency string from path/*/deps-file (if it exists)
- deps: parsed dependency string"
[paths & [deps-file]]
(P/let [deps-file (or deps-file "deps")
deps (P/->>
(for [path paths]
(P/let [pfile? (or (= "-" path)
(P/-> (fs/stat path) .isFile))]
(if pfile?
(P/->> (load-json path)
(map (fn [[k vs]]
(if (and (not (nil? vs))
(not (sequential? vs)))
(throw (ex-info
(str "Dep value for " k
" must be an array") {})))
{:node k
:path path
:deps (for [v vs]
(if (sequential? v)
{:or v}
(keywordize-keys v)))})))
(P/->> (fs/readdir path #js {:withFileTypes true})
(filter #(.isDirectory %))
(map #(P/let [node (.-name %)
npath (path/join path node)
df (path/join npath deps-file)
ds (P/catch (fs/readFile df "utf8")
(fn [] ""))]
{:node node
:path npath
:dep-str ds
:deps (parse-dep-str ds)}))
P/all))))
P/all
(mapcat identity))
errs (for [[n cnt] (frequencies (map :node deps))
:when (> cnt 1)]
(str "Node " n " appears in multiple places: "
(S/join
", " (map :path (filter #(= n (:node %)) deps)))))]
(when (seq errs)
(throw (ex-info (S/join "; " errs) {})))
(zipmap (map :node deps) deps)))
(defn print-deps
"Print nodes using data from deps (in format)"
[format nodes deps]
(condp = format
"nodes" (println (S/join " " nodes))
"paths" (println (S/join "\n" (for [n nodes]
(str n "=" (get-in deps [n :path])))))
"json" (println (js/JSON.stringify
(clj->js (for [n nodes]
(get deps n {:node n :deps []})))))))
(defn main
"Load node directory deps files found under path (--path) and then
print the best resolution and order that fulfills the <dep-str>
strings. Output format is selected by --format."
[argv]
(P/catch
(P/let [opts (parse-args argv)
start-dep-str (S/join "," (get opts "<dep-str>"))
deps (load-deps-files (S/split (get opts "--path") #"[:,]"))
dep-graph (assoc (zipmap (keys deps) (map :deps (vals deps)))
:START (parse-dep-str start-dep-str))
res-deps (->> (resolve-dep-order dep-graph :START)
(filter #(not= :START %)))]
(print-deps (get opts "--format") res-deps deps))
(fn [err]
(binding [*print-fn* *print-err-fn*]
(println "Error:" (or (.-message err) err)))
(js/process.exit 1))))
(main *command-line-args*)