rlm@10
|
1 ; Copyright (c) Chris Houser, Jan 2009. All rights reserved.
|
rlm@10
|
2 ; The use and distribution terms for this software are covered by the
|
rlm@10
|
3 ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
rlm@10
|
4 ; which can be found in the file epl-v10.html at the root of this distribution.
|
rlm@10
|
5 ; By using this software in any fashion, you are agreeing to be bound by
|
rlm@10
|
6 ; the terms of this license.
|
rlm@10
|
7 ; You must not remove this notice, or any other, from this software.
|
rlm@10
|
8
|
rlm@10
|
9 ; :dir and :env options added by Stuart Halloway
|
rlm@10
|
10
|
rlm@10
|
11 ; Conveniently launch a sub-process providing to its stdin and
|
rlm@10
|
12 ; collecting its stdout
|
rlm@10
|
13
|
rlm@10
|
14 ;; DEPRECATED in 1.2: Promoted to clojure.java.shell
|
rlm@10
|
15
|
rlm@10
|
16 (ns
|
rlm@10
|
17 ^{:author "Chris Houser",
|
rlm@10
|
18 :deprecated "1.2"
|
rlm@10
|
19 :doc "Conveniently launch a sub-process providing to its stdin and
|
rlm@10
|
20 collecting its stdout"}
|
rlm@10
|
21 clojure.contrib.shell
|
rlm@10
|
22 (:import (java.io InputStreamReader OutputStreamWriter)))
|
rlm@10
|
23
|
rlm@10
|
24 (def *sh-dir* nil)
|
rlm@10
|
25 (def *sh-env* nil)
|
rlm@10
|
26
|
rlm@10
|
27 (defmacro with-sh-dir [dir & forms]
|
rlm@10
|
28 "Sets the directory for use with sh, see sh for details."
|
rlm@10
|
29 `(binding [*sh-dir* ~dir]
|
rlm@10
|
30 ~@forms))
|
rlm@10
|
31
|
rlm@10
|
32 (defmacro with-sh-env [env & forms]
|
rlm@10
|
33 "Sets the environment for use with sh, see sh for details."
|
rlm@10
|
34 `(binding [*sh-env* ~env]
|
rlm@10
|
35 ~@forms))
|
rlm@10
|
36
|
rlm@10
|
37 (defn- stream-seq
|
rlm@10
|
38 "Takes an InputStream and returns a lazy seq of integers from the stream."
|
rlm@10
|
39 [stream]
|
rlm@10
|
40 (take-while #(>= % 0) (repeatedly #(.read stream))))
|
rlm@10
|
41
|
rlm@10
|
42 (defn- aconcat
|
rlm@10
|
43 "Concatenates arrays of given type."
|
rlm@10
|
44 [type & xs]
|
rlm@10
|
45 (let [target (make-array type (apply + (map count xs)))]
|
rlm@10
|
46 (loop [i 0 idx 0]
|
rlm@10
|
47 (when-let [a (nth xs i nil)]
|
rlm@10
|
48 (System/arraycopy a 0 target idx (count a))
|
rlm@10
|
49 (recur (inc i) (+ idx (count a)))))
|
rlm@10
|
50 target))
|
rlm@10
|
51
|
rlm@10
|
52 (defn- parse-args
|
rlm@10
|
53 "Takes a seq of 'sh' arguments and returns a map of option keywords
|
rlm@10
|
54 to option values."
|
rlm@10
|
55 [args]
|
rlm@10
|
56 (loop [[arg :as args] args opts {:cmd [] :out "UTF-8" :dir *sh-dir* :env *sh-env*}]
|
rlm@10
|
57 (if-not args
|
rlm@10
|
58 opts
|
rlm@10
|
59 (if (keyword? arg)
|
rlm@10
|
60 (recur (nnext args) (assoc opts arg (second args)))
|
rlm@10
|
61 (recur (next args) (update-in opts [:cmd] conj arg))))))
|
rlm@10
|
62
|
rlm@10
|
63 (defn- as-env-key [arg]
|
rlm@10
|
64 "Helper so that callers can use symbols, keywords, or strings
|
rlm@10
|
65 when building an environment map."
|
rlm@10
|
66 (cond
|
rlm@10
|
67 (symbol? arg) (name arg)
|
rlm@10
|
68 (keyword? arg) (name arg)
|
rlm@10
|
69 (string? arg) arg))
|
rlm@10
|
70
|
rlm@10
|
71 (defn- as-file [arg]
|
rlm@10
|
72 "Helper so that callers can pass a String for the :dir to sh."
|
rlm@10
|
73 (cond
|
rlm@10
|
74 (string? arg) (java.io.File. arg)
|
rlm@10
|
75 (nil? arg) nil
|
rlm@10
|
76 (instance? java.io.File arg) arg))
|
rlm@10
|
77
|
rlm@10
|
78 (defn- as-env-string [arg]
|
rlm@10
|
79 "Helper so that callers can pass a Clojure map for the :env to sh."
|
rlm@10
|
80 (cond
|
rlm@10
|
81 (nil? arg) nil
|
rlm@10
|
82 (map? arg) (into-array String (map (fn [[k v]] (str (as-env-key k) "=" v)) arg))
|
rlm@10
|
83 true arg))
|
rlm@10
|
84
|
rlm@10
|
85
|
rlm@10
|
86 (defn sh
|
rlm@10
|
87 "Passes the given strings to Runtime.exec() to launch a sub-process.
|
rlm@10
|
88
|
rlm@10
|
89 Options are
|
rlm@10
|
90
|
rlm@10
|
91 :in may be given followed by a String specifying text to be fed to the
|
rlm@10
|
92 sub-process's stdin.
|
rlm@10
|
93 :out option may be given followed by :bytes or a String. If a String
|
rlm@10
|
94 is given, it will be used as a character encoding name (for
|
rlm@10
|
95 example \"UTF-8\" or \"ISO-8859-1\") to convert the
|
rlm@10
|
96 sub-process's stdout to a String which is returned.
|
rlm@10
|
97 If :bytes is given, the sub-process's stdout will be stored in
|
rlm@10
|
98 a byte array and returned. Defaults to UTF-8.
|
rlm@10
|
99 :return-map
|
rlm@10
|
100 when followed by boolean true, sh returns a map of
|
rlm@10
|
101 :exit => sub-process's exit code
|
rlm@10
|
102 :out => sub-process's stdout (as byte[] or String)
|
rlm@10
|
103 :err => sub-process's stderr (as byte[] or String)
|
rlm@10
|
104 when not given or followed by false, sh returns a single
|
rlm@10
|
105 array or String of the sub-process's stdout followed by its
|
rlm@10
|
106 stderr
|
rlm@10
|
107 :env override the process env with a map (or the underlying Java
|
rlm@10
|
108 String[] if you are a masochist).
|
rlm@10
|
109 :dir override the process dir with a String or java.io.File.
|
rlm@10
|
110
|
rlm@10
|
111 You can bind :env or :dir for multiple operations using with-sh-env
|
rlm@10
|
112 and with-sh-dir."
|
rlm@10
|
113 [& args]
|
rlm@10
|
114 (let [opts (parse-args args)
|
rlm@10
|
115 proc (.exec (Runtime/getRuntime)
|
rlm@10
|
116 (into-array (:cmd opts))
|
rlm@10
|
117 (as-env-string (:env opts))
|
rlm@10
|
118 (as-file (:dir opts)))]
|
rlm@10
|
119 (if (:in opts)
|
rlm@10
|
120 (with-open [osw (OutputStreamWriter. (.getOutputStream proc))]
|
rlm@10
|
121 (.write osw (:in opts)))
|
rlm@10
|
122 (.close (.getOutputStream proc)))
|
rlm@10
|
123 (with-open [stdout (.getInputStream proc)
|
rlm@10
|
124 stderr (.getErrorStream proc)]
|
rlm@10
|
125 (let [[[out err] combine-fn]
|
rlm@10
|
126 (if (= (:out opts) :bytes)
|
rlm@10
|
127 [(for [strm [stdout stderr]]
|
rlm@10
|
128 (into-array Byte/TYPE (map byte (stream-seq strm))))
|
rlm@10
|
129 #(aconcat Byte/TYPE %1 %2)]
|
rlm@10
|
130 [(for [strm [stdout stderr]]
|
rlm@10
|
131 (apply str (map char (stream-seq
|
rlm@10
|
132 (InputStreamReader. strm (:out opts))))))
|
rlm@10
|
133 str])
|
rlm@10
|
134 exit-code (.waitFor proc)]
|
rlm@10
|
135 (if (:return-map opts)
|
rlm@10
|
136 {:exit exit-code :out out :err err}
|
rlm@10
|
137 (combine-fn out err))))))
|
rlm@10
|
138
|
rlm@10
|
139 (comment
|
rlm@10
|
140
|
rlm@10
|
141 (println (sh "ls" "-l"))
|
rlm@10
|
142 (println (sh "ls" "-l" "/no-such-thing"))
|
rlm@10
|
143 (println (sh "sed" "s/[aeiou]/oo/g" :in "hello there\n"))
|
rlm@10
|
144 (println (sh "cat" :in "x\u25bax\n"))
|
rlm@10
|
145 (println (sh "echo" "x\u25bax"))
|
rlm@10
|
146 (println (sh "echo" "x\u25bax" :out "ISO-8859-1")) ; reads 4 single-byte chars
|
rlm@10
|
147 (println (sh "cat" "myimage.png" :out :bytes)) ; reads binary file into bytes[]
|
rlm@10
|
148
|
rlm@10
|
149 )
|