Mercurial > lasercutter
diff 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 |
line wrap: on
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/clojure/contrib/shell.clj Sat Aug 21 06:25:44 2010 -0400 1.3 @@ -0,0 +1,149 @@ 1.4 +; Copyright (c) Chris Houser, Jan 2009. All rights reserved. 1.5 +; The use and distribution terms for this software are covered by the 1.6 +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 1.7 +; which can be found in the file epl-v10.html at the root of this distribution. 1.8 +; By using this software in any fashion, you are agreeing to be bound by 1.9 +; the terms of this license. 1.10 +; You must not remove this notice, or any other, from this software. 1.11 + 1.12 +; :dir and :env options added by Stuart Halloway 1.13 + 1.14 +; Conveniently launch a sub-process providing to its stdin and 1.15 +; collecting its stdout 1.16 + 1.17 +;; DEPRECATED in 1.2: Promoted to clojure.java.shell 1.18 + 1.19 +(ns 1.20 + ^{:author "Chris Houser", 1.21 + :deprecated "1.2" 1.22 + :doc "Conveniently launch a sub-process providing to its stdin and 1.23 +collecting its stdout"} 1.24 + clojure.contrib.shell 1.25 + (:import (java.io InputStreamReader OutputStreamWriter))) 1.26 + 1.27 +(def *sh-dir* nil) 1.28 +(def *sh-env* nil) 1.29 + 1.30 +(defmacro with-sh-dir [dir & forms] 1.31 + "Sets the directory for use with sh, see sh for details." 1.32 + `(binding [*sh-dir* ~dir] 1.33 + ~@forms)) 1.34 + 1.35 +(defmacro with-sh-env [env & forms] 1.36 + "Sets the environment for use with sh, see sh for details." 1.37 + `(binding [*sh-env* ~env] 1.38 + ~@forms)) 1.39 + 1.40 +(defn- stream-seq 1.41 + "Takes an InputStream and returns a lazy seq of integers from the stream." 1.42 + [stream] 1.43 + (take-while #(>= % 0) (repeatedly #(.read stream)))) 1.44 + 1.45 +(defn- aconcat 1.46 + "Concatenates arrays of given type." 1.47 + [type & xs] 1.48 + (let [target (make-array type (apply + (map count xs)))] 1.49 + (loop [i 0 idx 0] 1.50 + (when-let [a (nth xs i nil)] 1.51 + (System/arraycopy a 0 target idx (count a)) 1.52 + (recur (inc i) (+ idx (count a))))) 1.53 + target)) 1.54 + 1.55 +(defn- parse-args 1.56 + "Takes a seq of 'sh' arguments and returns a map of option keywords 1.57 + to option values." 1.58 + [args] 1.59 + (loop [[arg :as args] args opts {:cmd [] :out "UTF-8" :dir *sh-dir* :env *sh-env*}] 1.60 + (if-not args 1.61 + opts 1.62 + (if (keyword? arg) 1.63 + (recur (nnext args) (assoc opts arg (second args))) 1.64 + (recur (next args) (update-in opts [:cmd] conj arg)))))) 1.65 + 1.66 +(defn- as-env-key [arg] 1.67 + "Helper so that callers can use symbols, keywords, or strings 1.68 + when building an environment map." 1.69 + (cond 1.70 + (symbol? arg) (name arg) 1.71 + (keyword? arg) (name arg) 1.72 + (string? arg) arg)) 1.73 + 1.74 +(defn- as-file [arg] 1.75 + "Helper so that callers can pass a String for the :dir to sh." 1.76 + (cond 1.77 + (string? arg) (java.io.File. arg) 1.78 + (nil? arg) nil 1.79 + (instance? java.io.File arg) arg)) 1.80 + 1.81 +(defn- as-env-string [arg] 1.82 + "Helper so that callers can pass a Clojure map for the :env to sh." 1.83 + (cond 1.84 + (nil? arg) nil 1.85 + (map? arg) (into-array String (map (fn [[k v]] (str (as-env-key k) "=" v)) arg)) 1.86 + true arg)) 1.87 + 1.88 + 1.89 +(defn sh 1.90 + "Passes the given strings to Runtime.exec() to launch a sub-process. 1.91 + 1.92 + Options are 1.93 + 1.94 + :in may be given followed by a String specifying text to be fed to the 1.95 + sub-process's stdin. 1.96 + :out option may be given followed by :bytes or a String. If a String 1.97 + is given, it will be used as a character encoding name (for 1.98 + example \"UTF-8\" or \"ISO-8859-1\") to convert the 1.99 + sub-process's stdout to a String which is returned. 1.100 + If :bytes is given, the sub-process's stdout will be stored in 1.101 + a byte array and returned. Defaults to UTF-8. 1.102 + :return-map 1.103 + when followed by boolean true, sh returns a map of 1.104 + :exit => sub-process's exit code 1.105 + :out => sub-process's stdout (as byte[] or String) 1.106 + :err => sub-process's stderr (as byte[] or String) 1.107 + when not given or followed by false, sh returns a single 1.108 + array or String of the sub-process's stdout followed by its 1.109 + stderr 1.110 + :env override the process env with a map (or the underlying Java 1.111 + String[] if you are a masochist). 1.112 + :dir override the process dir with a String or java.io.File. 1.113 + 1.114 + You can bind :env or :dir for multiple operations using with-sh-env 1.115 + and with-sh-dir." 1.116 + [& args] 1.117 + (let [opts (parse-args args) 1.118 + proc (.exec (Runtime/getRuntime) 1.119 + (into-array (:cmd opts)) 1.120 + (as-env-string (:env opts)) 1.121 + (as-file (:dir opts)))] 1.122 + (if (:in opts) 1.123 + (with-open [osw (OutputStreamWriter. (.getOutputStream proc))] 1.124 + (.write osw (:in opts))) 1.125 + (.close (.getOutputStream proc))) 1.126 + (with-open [stdout (.getInputStream proc) 1.127 + stderr (.getErrorStream proc)] 1.128 + (let [[[out err] combine-fn] 1.129 + (if (= (:out opts) :bytes) 1.130 + [(for [strm [stdout stderr]] 1.131 + (into-array Byte/TYPE (map byte (stream-seq strm)))) 1.132 + #(aconcat Byte/TYPE %1 %2)] 1.133 + [(for [strm [stdout stderr]] 1.134 + (apply str (map char (stream-seq 1.135 + (InputStreamReader. strm (:out opts)))))) 1.136 + str]) 1.137 + exit-code (.waitFor proc)] 1.138 + (if (:return-map opts) 1.139 + {:exit exit-code :out out :err err} 1.140 + (combine-fn out err)))))) 1.141 + 1.142 +(comment 1.143 + 1.144 +(println (sh "ls" "-l")) 1.145 +(println (sh "ls" "-l" "/no-such-thing")) 1.146 +(println (sh "sed" "s/[aeiou]/oo/g" :in "hello there\n")) 1.147 +(println (sh "cat" :in "x\u25bax\n")) 1.148 +(println (sh "echo" "x\u25bax")) 1.149 +(println (sh "echo" "x\u25bax" :out "ISO-8859-1")) ; reads 4 single-byte chars 1.150 +(println (sh "cat" "myimage.png" :out :bytes)) ; reads binary file into bytes[] 1.151 + 1.152 +)