diff options
Diffstat (limited to 'modules/shell')
-rw-r--r-- | modules/shell/pom.xml | 16 | ||||
-rw-r--r-- | modules/shell/src/main/clojure/clojure/contrib/shell.clj | 149 |
2 files changed, 165 insertions, 0 deletions
diff --git a/modules/shell/pom.xml b/modules/shell/pom.xml new file mode 100644 index 00000000..a74b677f --- /dev/null +++ b/modules/shell/pom.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http//www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.clojure.contrib</groupId> + <artifactId>parent</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../parent</relativePath> + </parent> + <artifactId>shell</artifactId> + <dependencies> + </dependencies> +</project>
\ No newline at end of file diff --git a/modules/shell/src/main/clojure/clojure/contrib/shell.clj b/modules/shell/src/main/clojure/clojure/contrib/shell.clj new file mode 100644 index 00000000..63467664 --- /dev/null +++ b/modules/shell/src/main/clojure/clojure/contrib/shell.clj @@ -0,0 +1,149 @@ +; Copyright (c) Chris Houser, Jan 2009. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +; :dir and :env options added by Stuart Halloway + +; Conveniently launch a sub-process providing to its stdin and +; collecting its stdout + +;; DEPRECATED in 1.2: Promoted to clojure.java.shell + +(ns + ^{:author "Chris Houser", + :deprecated "1.2" + :doc "Conveniently launch a sub-process providing to its stdin and +collecting its stdout"} + clojure.contrib.shell + (:import (java.io InputStreamReader OutputStreamWriter))) + +(def *sh-dir* nil) +(def *sh-env* nil) + +(defmacro with-sh-dir [dir & forms] + "Sets the directory for use with sh, see sh for details." + `(binding [*sh-dir* ~dir] + ~@forms)) + +(defmacro with-sh-env [env & forms] + "Sets the environment for use with sh, see sh for details." + `(binding [*sh-env* ~env] + ~@forms)) + +(defn- stream-seq + "Takes an InputStream and returns a lazy seq of integers from the stream." + [stream] + (take-while #(>= % 0) (repeatedly #(.read stream)))) + +(defn- aconcat + "Concatenates arrays of given type." + [type & xs] + (let [target (make-array type (apply + (map count xs)))] + (loop [i 0 idx 0] + (when-let [a (nth xs i nil)] + (System/arraycopy a 0 target idx (count a)) + (recur (inc i) (+ idx (count a))))) + target)) + +(defn- parse-args + "Takes a seq of 'sh' arguments and returns a map of option keywords + to option values." + [args] + (loop [[arg :as args] args opts {:cmd [] :out "UTF-8" :dir *sh-dir* :env *sh-env*}] + (if-not args + opts + (if (keyword? arg) + (recur (nnext args) (assoc opts arg (second args))) + (recur (next args) (update-in opts [:cmd] conj arg)))))) + +(defn- as-env-key [arg] + "Helper so that callers can use symbols, keywords, or strings + when building an environment map." + (cond + (symbol? arg) (name arg) + (keyword? arg) (name arg) + (string? arg) arg)) + +(defn- as-file [arg] + "Helper so that callers can pass a String for the :dir to sh." + (cond + (string? arg) (java.io.File. arg) + (nil? arg) nil + (instance? java.io.File arg) arg)) + +(defn- as-env-string [arg] + "Helper so that callers can pass a Clojure map for the :env to sh." + (cond + (nil? arg) nil + (map? arg) (into-array String (map (fn [[k v]] (str (as-env-key k) "=" v)) arg)) + true arg)) + + +(defn sh + "Passes the given strings to Runtime.exec() to launch a sub-process. + + Options are + + :in may be given followed by a String specifying text to be fed to the + sub-process's stdin. + :out option may be given followed by :bytes or a String. If a String + is given, it will be used as a character encoding name (for + example \"UTF-8\" or \"ISO-8859-1\") to convert the + sub-process's stdout to a String which is returned. + If :bytes is given, the sub-process's stdout will be stored in + a byte array and returned. Defaults to UTF-8. + :return-map + when followed by boolean true, sh returns a map of + :exit => sub-process's exit code + :out => sub-process's stdout (as byte[] or String) + :err => sub-process's stderr (as byte[] or String) + when not given or followed by false, sh returns a single + array or String of the sub-process's stdout followed by its + stderr + :env override the process env with a map (or the underlying Java + String[] if you are a masochist). + :dir override the process dir with a String or java.io.File. + + You can bind :env or :dir for multiple operations using with-sh-env + and with-sh-dir." + [& args] + (let [opts (parse-args args) + proc (.exec (Runtime/getRuntime) + (into-array (:cmd opts)) + (as-env-string (:env opts)) + (as-file (:dir opts)))] + (if (:in opts) + (with-open [osw (OutputStreamWriter. (.getOutputStream proc))] + (.write osw (:in opts))) + (.close (.getOutputStream proc))) + (with-open [stdout (.getInputStream proc) + stderr (.getErrorStream proc)] + (let [[[out err] combine-fn] + (if (= (:out opts) :bytes) + [(for [strm [stdout stderr]] + (into-array Byte/TYPE (map byte (stream-seq strm)))) + #(aconcat Byte/TYPE %1 %2)] + [(for [strm [stdout stderr]] + (apply str (map char (stream-seq + (InputStreamReader. strm (:out opts)))))) + str]) + exit-code (.waitFor proc)] + (if (:return-map opts) + {:exit exit-code :out out :err err} + (combine-fn out err)))))) + +(comment + +(println (sh "ls" "-l")) +(println (sh "ls" "-l" "/no-such-thing")) +(println (sh "sed" "s/[aeiou]/oo/g" :in "hello there\n")) +(println (sh "cat" :in "x\u25bax\n")) +(println (sh "echo" "x\u25bax")) +(println (sh "echo" "x\u25bax" :out "ISO-8859-1")) ; reads 4 single-byte chars +(println (sh "cat" "myimage.png" :out :bytes)) ; reads binary file into bytes[] + +) |