Mercurial > lasercutter
comparison 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 |
comparison
equal
deleted
inserted
replaced
9:35cf337adfcf | 10:ef7dbbd6452c |
---|---|
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. | |
8 | |
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))) | |
17 | |
18 (def *sh-dir* nil) | |
19 (def *sh-env* nil) | |
20 | |
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)) | |
27 | |
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)) | |
34 | |
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)) | |
44 | |
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))])) | |
51 | |
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)) | |
59 | |
60 (defn- stream-to-bytes | |
61 [in] | |
62 (with-open [bout (ByteArrayOutputStream.)] | |
63 (copy in bout) | |
64 (.toByteArray bout))) | |
65 | |
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)))) | |
72 | |
73 (defn- stream-to-enc | |
74 [stream enc] | |
75 (if (= enc :bytes) | |
76 (stream-to-bytes stream) | |
77 (stream-to-string stream enc))) | |
78 | |
79 (defn sh | |
80 "Passes the given strings to Runtime.exec() to launch a sub-process. | |
81 | |
82 Options are | |
83 | |
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. | |
101 | |
102 You can bind :env or :dir for multiple operations using with-sh-env | |
103 and with-sh-dir. | |
104 | |
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})))) | |
131 | |
132 (comment | |
133 | |
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")) | |
142 | |
143 ) |