view src/clojure/java/shell.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
line wrap: on
line source
1 ; Copyright (c) Rich Hickey. All rights reserved.
2 ; The use and distribution terms for this software are covered by the
3 ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 ; which can be found in the file epl-v10.html at the root of this distribution.
5 ; By using this software in any fashion, you are agreeing to be bound by
6 ; the terms of this license.
7 ; You must not remove this notice, or any other, from this software.
9 (ns
10 ^{:author "Chris Houser, Stuart Halloway",
11 :doc "Conveniently launch a sub-process providing its stdin and
12 collecting its stdout"}
13 clojure.java.shell
14 (:use [clojure.java.io :only (as-file copy)])
15 (:import (java.io OutputStreamWriter ByteArrayOutputStream StringWriter)
16 (java.nio.charset Charset)))
18 (def *sh-dir* nil)
19 (def *sh-env* nil)
21 (defmacro with-sh-dir
22 "Sets the directory for use with sh, see sh for details."
23 {:added "1.2"}
24 [dir & forms]
25 `(binding [*sh-dir* ~dir]
26 ~@forms))
28 (defmacro with-sh-env
29 "Sets the environment for use with sh, see sh for details."
30 {:added "1.2"}
31 [env & forms]
32 `(binding [*sh-env* ~env]
33 ~@forms))
35 (defn- aconcat
36 "Concatenates arrays of given type."
37 [type & xs]
38 (let [target (make-array type (apply + (map count xs)))]
39 (loop [i 0 idx 0]
40 (when-let [a (nth xs i nil)]
41 (System/arraycopy a 0 target idx (count a))
42 (recur (inc i) (+ idx (count a)))))
43 target))
45 (defn- parse-args
46 [args]
47 (let [default-encoding "UTF-8" ;; see sh doc string
48 default-opts {:out-enc default-encoding :in-enc default-encoding :dir *sh-dir* :env *sh-env*}
49 [cmd opts] (split-with string? args)]
50 [cmd (merge default-opts (apply hash-map opts))]))
52 (defn- ^"[Ljava.lang.String;" as-env-strings
53 "Helper so that callers can pass a Clojure map for the :env to sh."
54 [arg]
55 (cond
56 (nil? arg) nil
57 (map? arg) (into-array String (map (fn [[k v]] (str (name k) "=" v)) arg))
58 true arg))
60 (defn- stream-to-bytes
61 [in]
62 (with-open [bout (ByteArrayOutputStream.)]
63 (copy in bout)
64 (.toByteArray bout)))
66 (defn- stream-to-string
67 ([in] (stream-to-string in (.name (Charset/defaultCharset))))
68 ([in enc]
69 (with-open [bout (StringWriter.)]
70 (copy in bout :encoding enc)
71 (.toString bout))))
73 (defn- stream-to-enc
74 [stream enc]
75 (if (= enc :bytes)
76 (stream-to-bytes stream)
77 (stream-to-string stream enc)))
79 (defn sh
80 "Passes the given strings to Runtime.exec() to launch a sub-process.
82 Options are
84 :in may be given followed by a String or byte array specifying input
85 to be fed to the sub-process's stdin.
86 :in-enc option may be given followed by a String, used as a character
87 encoding name (for example \"UTF-8\" or \"ISO-8859-1\") to
88 convert the input string specified by the :in option to the
89 sub-process's stdin. Defaults to UTF-8.
90 If the :in option provides a byte array, then the bytes are passed
91 unencoded, and this option is ignored.
92 :out-enc option may be given followed by :bytes or a String. If a
93 String is given, it will be used as a character encoding
94 name (for example \"UTF-8\" or \"ISO-8859-1\") to convert
95 the sub-process's stdout to a String which is returned.
96 If :bytes is given, the sub-process's stdout will be stored
97 in a byte array and returned. Defaults to UTF-8.
98 :env override the process env with a map (or the underlying Java
99 String[] if you are a masochist).
100 :dir override the process dir with a String or java.io.File.
102 You can bind :env or :dir for multiple operations using with-sh-env
103 and with-sh-dir.
105 sh returns a map of
106 :exit => sub-process's exit code
107 :out => sub-process's stdout (as byte[] or String)
108 :err => sub-process's stderr (String via platform default encoding)"
109 {:added "1.2"}
110 [& args]
111 (let [[cmd opts] (parse-args args)
112 proc (.exec (Runtime/getRuntime)
113 ^"[Ljava.lang.String;" (into-array cmd)
114 (as-env-strings (:env opts))
115 (as-file (:dir opts)))
116 {:keys [in in-enc out-enc]} opts]
117 (if in
118 (future
119 (if (instance? (class (byte-array 0)) in)
120 (with-open [os (.getOutputStream proc)]
121 (.write os ^"[B" in))
122 (with-open [osw (OutputStreamWriter. (.getOutputStream proc) ^String in-enc)]
123 (.write osw ^String in))))
124 (.close (.getOutputStream proc)))
125 (with-open [stdout (.getInputStream proc)
126 stderr (.getErrorStream proc)]
127 (let [out (future (stream-to-enc stdout out-enc))
128 err (future (stream-to-string stderr))
129 exit-code (.waitFor proc)]
130 {:exit exit-code :out @out :err @err}))))
132 (comment
134 (println (sh "ls" "-l"))
135 (println (sh "ls" "-l" "/no-such-thing"))
136 (println (sh "sed" "s/[aeiou]/oo/g" :in "hello there\n"))
137 (println (sh "cat" :in "x\u25bax\n"))
138 (println (sh "echo" "x\u25bax"))
139 (println (sh "echo" "x\u25bax" :out-enc "ISO-8859-1")) ; reads 4 single-byte chars
140 (println (sh "cat" "myimage.png" :out-enc :bytes)) ; reads binary file into bytes[]
141 (println (sh "cmd" "/c dir 1>&2"))
143 )