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