This repository has been archived by the owner on Jun 3, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
shell.cljs
88 lines (77 loc) · 2.58 KB
/
shell.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
(ns re-conf.resources.shell
(:require-macros
[clojure.core.strint :refer (<<)]
[cljs.core.async.macros :refer [go]])
(:require
[re-conf.resources.log :refer (channel? info debug error)]
[cuerdas.core :as str]
[cljs.nodejs :as nodejs]
[re-conf.resources.common :refer (run)]
[cljs.core.match :refer-macros [match]]
[cljs.core.async :as async :refer [put! <! chan alts! timeout take!]]))
(def spawn (.-spawn (js/require "child_process")))
(def spawn-sync (.-spawnSync (js/require "child_process")))
(defn exec-chan
"spawns a child process for cmd with args. routes stdout, stderr, and
the exit code to a channel. returns the channel immediately."
[cmd args opts]
(let [c (chan) p (spawn cmd args opts)]
(.on (.-stdout p) "data" #(put! c [:out (str %)]))
(.on (.-stderr p) "data" #(put! c [:err (str %)]))
(.on p "close" #(put! c [:exit (str %)]))
(.on p "error" (fn [e] (put! c [:error e])))
c))
(defn- execute
"Executes cmd with args. returns a channel immediately which
will eventually receive a result vector of pairs [:kind data-str]
with the last pair being [:exit code]"
[cmd args opts]
(let [c (exec-chan cmd (clj->js args) (clj->js opts))]
(go
(loop [output (<! c) result {}]
(match output
[:exit code] (assoc result :exit (str/parse-int code))
[:error e] {:error e}
:else
(recur (<! c) (update result (first output) str (second output))))))))
(defn opts-split [as]
(let [[args opts] (split-with string? as)]
[args (apply hash-map opts)]))
(defn apply-options [args opts]
(let []
(cond->> args
(opts :sudo) (into ["/usr/bin/sudo"])
(opts :dry) (or ["echo" "'dry run!'"]))))
(defn sh [& as]
(go
(let [[cmdline opts] (opts-split as)
[cmd & args] (apply-options cmdline opts)]
(try
(let [{:keys [exit] :as r} (into {} (<! (execute cmd args opts)))]
(if (= 0 exit)
{:ok r}
{:error r}))
(catch js/Error e
{:error e})))))
(defn exec
"Shell execution resource"
[c & args]
(if (channel? c)
(run c sh args)
(run nil sh (conj args c))))
(defn exec-sync
"Sync exec (not resource)"
[cmd & as]
(let [[args opts] (opts-split as)]
(js->clj
(spawn-sync cmd (clj->js args) (clj->js opts)) :keywordize-keys true)))
(defn unless
"Run shell only if c returns :ok"
[c & args]
(go
(let [{:keys [ok error] :as m} (<! c)]
(if-not ok
(<! (apply exec args))
{:ok (<< "skipping due to ~{error}")}))))
(comment
(info (sh "ls" "/foo" :sudo true) ::take))