forked from Raynes/conch
/
low_level.clj
116 lines (105 loc) · 4.3 KB
/
low_level.clj
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
(ns me.raynes.conch.low-level
"A simple but flexible library for shelling out from Clojure."
(:refer-clojure :exclude [flush read-line])
(:require [clojure.java.io :as io])
(:import (java.util.concurrent TimeUnit TimeoutException)))
(defn proc
"Spin off another process. Returns the process's input stream,
output stream, and err stream as a map of :in, :out, and :err keys
If passed the optional :dir and/or :env keyword options, the dir
and enviroment will be set to what you specify. If you pass
:verbose and it is true, commands will be printed. If it is set to
:very, environment variables passed, dir, and the command will be
printed. If passed the :clear-env keyword option, then the process
will not inherit its environment from its parent process."
[& args]
(let [[cmd args] (split-with (complement keyword?) args)
args (apply hash-map args)
builder (ProcessBuilder. (into-array String cmd))
env (.environment builder)]
(when (:clear-env args)
(.clear env))
(doseq [[k v] (:env args)]
(.put env k v))
(when-let [dir (:dir args)]
(.directory builder (io/file dir)))
(when (:verbose args) (apply println cmd))
(when (= :very (:verbose args))
(when-let [env (:env args)] (prn env))
(when-let [dir (:dir args)] (prn dir)))
(when (:redirect-err args)
(.redirectErrorStream builder true))
(let [process (.start builder)]
{:out (.getInputStream process)
:in (.getOutputStream process)
:err (.getErrorStream process)
:process process})))
(defn destroy
"Destroy a process."
[process]
(.destroy (:process process)))
;; .waitFor returns the exit code. This makes this function useful for
;; both getting an exit code and stopping the thread until a process
;; terminates.
(defn exit-code
"Waits for the process to terminate (blocking the thread) and returns
the exit code. If timeout is passed, it is assumed to be milliseconds
to wait for the process to exit. If it does not exit in time, it is
killed (with or without fire)."
([process] (.waitFor (:process process)))
([process timeout]
(try
(.get (future (.waitFor (:process process))) timeout TimeUnit/MILLISECONDS)
(catch Exception e
(if (or (instance? TimeoutException e)
(instance? TimeoutException (.getCause e)))
(do (destroy process)
:timeout)
(throw e))))))
(defn flush
"Flush the output stream of a process."
[process]
(.flush (:in process)))
(defn done
"Close the process's output stream (sending EOF)."
[proc]
(-> proc :in .close))
(defn stream-to
"Stream :out or :err from a process to an ouput stream.
Options passed are fed to clojure.java.io/copy. They are :encoding to
set the encoding and :buffer-size to set the size of the buffer.
:encoding defaults to UTF-8 and :buffer-size to 1024."
[process from to & args]
(apply io/copy (process from) to args))
(defn feed-from
"Feed to a process's input stream with optional. Options passed are
fed to clojure.java.io/copy. They are :encoding to set the encoding
and :buffer-size to set the size of the buffer. :encoding defaults to
UTF-8 and :buffer-size to 1024. If :flush is specified and is false,
the process will be flushed after writing."
[process from & {flush? :flush :or {flush? true} :as all}]
(apply io/copy from (:in process) all)
(when flush? (flush process)))
(defn stream-to-string
"Streams the output of the process to a string and returns it."
[process from & args]
(with-open [writer (java.io.StringWriter.)]
(apply stream-to process from writer args)
(str writer)))
;; The writer that Clojure wraps System/out in for *out* seems to buffer
;; things instead of writing them immediately. This wont work if you
;; really want to stream stuff, so we'll just skip it and throw our data
;; directly at System/out.
(defn stream-to-out
"Streams the output of the process to System/out"
[process from & args]
(apply stream-to process from (System/out) args))
(defn feed-from-string
"Feed the process some data from a string."
[process s & args]
(apply feed-from process (java.io.StringReader. s) args))
(defn read-line
"Read a line from a process' :out or :err."
[process from]
(binding [*in* (io/reader (from process))]
(clojure.core/read-line)))