Skip to content

Commit e8fb8b6

Browse files
committed
initial export from monorepo
0 parents  commit e8fb8b6

File tree

5 files changed

+365
-0
lines changed

5 files changed

+365
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.vero
2+
.clj-kondo

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# vero

examples/minimal.clj

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/sh
2+
3+
#_(
4+
"exec" "bb" "--classpath" "../src" "$0" "$@"
5+
)
6+
7+
(require '[ekspono.vero :as vero])
8+
9+
(defn hello
10+
[]
11+
(println "world!"))
12+
13+
(def usage "minimal.clj
14+
15+
Usage:
16+
minimal.clj hello
17+
18+
Options:
19+
-h --help Show this screen")
20+
21+
(def config
22+
{:usage usage
23+
:vars []})
24+
25+
(vero/start config *command-line-args*
26+
(fn [opts]
27+
(cond
28+
(:hello opts) (hello))))

src/ekspono/vero.clj

+284
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
(ns ekspono.vero
2+
(:require [clojure.tools.cli :refer [parse-opts]]
3+
[clojure.java.shell :refer [sh]]
4+
[clojure.string :as string]
5+
[clojure.edn :as edn]
6+
[clojure.java.io :as io]
7+
[ekspono.vero.docopt-parser :as docopt-parser]))
8+
9+
(def config (atom {}))
10+
11+
(def alphanumeric "0123456789abcdefghijklmnopqrstuvwxyz")
12+
13+
(defn rand-alphanumeric
14+
[len]
15+
(apply str (take len (repeatedly #(rand-nth alphanumeric)))))
16+
17+
(defn get-env-vars
18+
[]
19+
(:vars @config))
20+
21+
(defn exit [status msg]
22+
(println msg)
23+
(System/exit status))
24+
25+
(defn run-command [& args]
26+
(apply sh "bash" "-c" args))
27+
28+
(defn var
29+
[var-name]
30+
(get-in @config [:vars var-name]))
31+
32+
(defn set-var
33+
[var-name val]
34+
(swap! config assoc-in [:vars var-name] val))
35+
36+
(defn file
37+
[file-name]
38+
(get-in @config [:files file-name]))
39+
40+
(defn file-exists?
41+
[path]
42+
(.exists (io/file path)))
43+
44+
(defn- load-env-file
45+
[f]
46+
(let [contents (slurp f)]
47+
(->> (string/split contents #"\n")
48+
(filter (fn [s] (not (re-matches #"#.*" s))))
49+
(map (fn [s] (as-> s $
50+
(clojure.string/replace $ #"export\s" "")
51+
(string/trim $)
52+
(string/split $ #"="))))
53+
(remove (fn [v] (not (= (count v) 2))))
54+
(into {}))))
55+
56+
(defn- run-with-env
57+
[cmd opts]
58+
(println "vero/running: " cmd)
59+
(let [dir (or (:dir opts) ".")
60+
env-vars (->> (for [[k v] (:env opts)]
61+
(str k "=" v))
62+
(into []))
63+
cmd (concat ["env"] env-vars cmd)
64+
pb (doto (ProcessBuilder. cmd)
65+
(.redirectErrorStream true)
66+
(.directory (clojure.java.io/file dir)))]
67+
(when (:interactive opts)
68+
(.inheritIO pb))
69+
(.start pb)))
70+
71+
(defn <-run
72+
([cmd opts]
73+
(let [cmd-str (map (fn [c] (str c)) cmd)
74+
process (run-with-env cmd-str opts)
75+
str-builder (StringBuilder.)]
76+
(with-open [rdr (clojure.java.io/reader (.getInputStream process))]
77+
(doseq [line (line-seq rdr)]
78+
(.append str-builder (str line "\n")))
79+
(.waitFor process))
80+
{:status (.exitValue process) :output (.toString str-builder)}))
81+
([cmd]
82+
(<-run cmd {})))
83+
84+
(defn run*
85+
([log-file cmd opts]
86+
(let [cmd-str (map (fn [c] (str c)) cmd)
87+
expected-exit-code (:expected-exit-code opts 0)
88+
process (run-with-env cmd-str opts)]
89+
(with-open [rdr (clojure.java.io/reader (.getInputStream process))]
90+
(doseq [line (line-seq rdr)]
91+
(if (not (nil? log-file))
92+
(spit log-file (str line "\n") :append true)
93+
(println (str line))))
94+
(.waitFor process))
95+
(let [exit-code (.exitValue process)]
96+
(when-not (:ignore-exit-code opts)
97+
(when-not (= exit-code expected-exit-code)
98+
(println (str "FATAL: Unexpected exit-code from command: " exit-code " (expected exit-code: " expected-exit-code ")"))
99+
(System/exit exit-code))))))
100+
([log-file cmd]
101+
(run* log-file cmd {})))
102+
103+
(defn run
104+
([cmd opts]
105+
(run* nil cmd (assoc opts :interactive true)))
106+
([cmd]
107+
(run* nil cmd {:interactive true})))
108+
109+
(defn berglas-access
110+
[url]
111+
(let [res (<-run ["scripts/secrets.sh" "access-url" url])]
112+
(if (= (:status res) 0)
113+
(->> (:output res)
114+
(string/trim))
115+
(throw (ex-info (str "ERROR: Command failed while loading config: " (:output res)) {})))))
116+
117+
(defn replace-several [s replacements]
118+
(reduce (fn [s [match replacement]]
119+
(clojure.string/replace s match replacement))
120+
s replacements))
121+
122+
(defn subst-vars
123+
[s vars]
124+
(->> (replace-several
125+
s
126+
(->> (for [[var-name exp] vars]
127+
(if (string? exp)
128+
[(str "${" var-name "}") exp]
129+
nil))
130+
(remove nil?)))
131+
(string/trim)
132+
(string/trim-newline)))
133+
134+
(defn subst
135+
[s]
136+
(subst-vars s (:vars @config)))
137+
138+
(defn process-directive
139+
[var-name exp selected-exp-type exp-fn vars]
140+
(let [exp-type (first exp)
141+
exp-content (second exp)
142+
exp-default-val (nth exp 2 nil)]
143+
(if (= selected-exp-type exp-type)
144+
(exp-fn var-name exp-content exp-default-val vars)
145+
[var-name exp])))
146+
147+
(defn process-config-var
148+
[var-entry selected-exp-type exp-fn vars]
149+
(let [var-name (first var-entry)
150+
exp (second var-entry)]
151+
(cond (vector? exp) (process-directive var-name exp selected-exp-type exp-fn vars)
152+
(string? exp) [var-name (subst-vars exp vars)]
153+
(boolean? exp) [var-name exp])))
154+
155+
(defn process-config-vars
156+
([vars processed-vars selected-exp-type exp-fn]
157+
(if (seq vars)
158+
(let [var-entry (first vars)
159+
processed-var (process-config-var var-entry
160+
selected-exp-type
161+
exp-fn
162+
(merge (->> vars (into {}))
163+
(->> processed-vars (into {}))))]
164+
(recur (rest vars)
165+
(conj processed-vars processed-var)
166+
selected-exp-type
167+
exp-fn))
168+
processed-vars))
169+
([vars selected-exp-type exp-fn]
170+
(process-config-vars
171+
vars
172+
[]
173+
selected-exp-type
174+
exp-fn)))
175+
176+
(defn read-files
177+
[files current-vars]
178+
(->> (for [[k path] files]
179+
(let [subst-path (subst-vars path current-vars)]
180+
(try
181+
(let [contents (slurp subst-path)]
182+
[k (edn/read-string contents)])
183+
(catch Exception e
184+
(println "Warning: file not found: " subst-path)
185+
[k nil]))))
186+
(into {})))
187+
188+
(defn read-config
189+
[raw-config options]
190+
(let [raw-vars (:vars raw-config)
191+
vars-tuples (->> raw-vars
192+
(partition 2)
193+
(map #(into [] %)))
194+
vars-opts (process-config-vars
195+
vars-tuples
196+
:opt
197+
(fn [var-name exp-content exp-default-val current-vars]
198+
(if-let [val (get options exp-content)]
199+
[var-name val]
200+
(if (some? exp-default-val)
201+
[var-name exp-default-val]
202+
[var-name nil]))))
203+
vars-cmd (process-config-vars
204+
vars-opts
205+
:cmd
206+
(fn [var-name exp-content exp-default-val current-vars]
207+
(let [subst-exp-content (subst-vars exp-content current-vars)
208+
result (run-command subst-exp-content)
209+
err (:err result)]
210+
(if (seq err)
211+
(throw (ex-info (str "ERROR: Command failed while loading config: " err) {}))
212+
[var-name (->> (:out result)
213+
(string/trim)
214+
(string/trim-newline))]))))
215+
vars-berglas (process-config-vars
216+
vars-cmd
217+
:berglas
218+
(fn [var-name exp-content exp-default-val current-vars]
219+
(let [secret (berglas-access exp-content)]
220+
[var-name secret])))
221+
files (read-files (:edn-files raw-config) vars-berglas)
222+
vars-files (process-config-vars
223+
vars-berglas
224+
:file
225+
(fn [var-name exp-content exp-default-val current-vars]
226+
(let [file-id (first exp-content)
227+
query (->> (rest exp-content)
228+
(map (fn [c]
229+
(if (string? c)
230+
(subst-vars c current-vars)
231+
c)))
232+
(map (fn [c]
233+
(if (and (string? c)
234+
(clojure.string/starts-with? c ":"))
235+
(keyword (subs c 1))
236+
c)))
237+
(into []))
238+
file (get files file-id)]
239+
(if-not (nil? file)
240+
(if-let [result (get-in file query)]
241+
[var-name result]
242+
(if-not (nil? exp-default-val)
243+
[var-name exp-default-val]
244+
(throw (ex-info (str "ERROR: Invalid file/query and no default value: " file-id " " query) {}))))
245+
(if-not (nil? exp-default-val)
246+
[var-name exp-default-val]
247+
(throw (ex-info (str "ERROR: Invalid file/query and no default value: " file-id " " query) {})))))))
248+
vars-str (process-config-vars
249+
vars-files
250+
:string
251+
(fn [var-name exp-content exp-default-val current-vars]
252+
(let [subst-exp-content (subst-vars exp-content current-vars)]
253+
[var-name subst-exp-content])))]
254+
(-> raw-config
255+
(assoc :vars (->> vars-str
256+
(into {})))
257+
(assoc :files files))))
258+
259+
(defn run-parallel
260+
[fn-sets]
261+
(let [results (for [fns fn-sets]
262+
(doall (pmap (fn [f] (apply f [])) fns)))]
263+
(flatten results)))
264+
265+
(defn process-config
266+
[raw-config cb options]
267+
268+
(let [c (read-config raw-config options)]
269+
(reset! config c)
270+
(cb options)))
271+
272+
(defn start
273+
[raw-config args cb]
274+
;; Prepare docopt dependency
275+
(let [repo-dir (->> (<-run ["git" "rev-parse" "--show-toplevel"])
276+
(:output)
277+
(string/trim)
278+
(string/trim-newline))]
279+
(docopt-parser/init repo-dir run))
280+
281+
(docopt-parser/parse (:usage raw-config)
282+
args
283+
(partial process-config raw-config cb)))
284+

src/ekspono/vero/docopt_parser.clj

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
(ns ekspono.vero.docopt-parser
2+
(:require [clojure.java.io :as io]
3+
[clojure.java.shell :refer [sh]]
4+
[clojure.string :as string]
5+
[babashka.classpath :refer [add-classpath]]))
6+
7+
(def download-docopt-cmd
8+
"#!/bin/bash
9+
10+
RESULT=$(clojure -Spath -Sdeps '{:deps {docopt/docopt {:git/url \"https://github.com/nubank/docopt.clj\"
11+
:sha \"12b997548381b607ddb246e4f4c54c01906e70aa\"}}}')
12+
13+
echo $RESULT > $1/vero-classpath")
14+
15+
(defn init
16+
[repo-dir run]
17+
(let [vero-dir (str repo-dir "/.vero")]
18+
(when-not (.exists (io/file (str vero-dir "/vero-classpath")))
19+
(println "Downloading vero dependency: docopt")
20+
(run ["mkdir" "-p" vero-dir])
21+
(spit (str vero-dir "/download-vero-deps.sh") download-docopt-cmd)
22+
(run ["chmod" "+x" (str vero-dir "/download-vero-deps.sh")])
23+
(run [(str vero-dir "/download-vero-deps.sh") vero-dir]))
24+
(let [cp (-> (:out (sh "cat" (str vero-dir "/vero-classpath"))))]
25+
(add-classpath cp)
26+
(require '[docopt.core :as docopt]))))
27+
28+
(defn- rename-key
29+
[key]
30+
(-> key
31+
(string/replace-first #"^--" "")
32+
(string/replace-first #"^-" "")
33+
(keyword)))
34+
35+
(defn- parse-options
36+
[arg-map]
37+
(->> (for [[key val] arg-map]
38+
[(rename-key key) val])
39+
(into {})))
40+
41+
(defn parse
42+
[usage args cb]
43+
(let [docopt (resolve 'docopt/docopt)]
44+
(docopt usage
45+
args
46+
(fn [arg-map]
47+
(let [options (parse-options arg-map)]
48+
(if (:help options)
49+
(println usage)
50+
(cb options)))))))

0 commit comments

Comments
 (0)