annotate 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
rev   line source
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 )