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 +)