From 7f2ad0d2f7defd46af306223bb29ba81e3782ed2 Mon Sep 17 00:00:00 2001 From: Elango Cheran Date: Mon, 23 Apr 2012 04:53:48 -0700 Subject: [PATCH] added run function to be versatile for piped commands and/or supplying a string and/or an input stream --- src/clj_commons_exec.clj | 105 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 4 deletions(-) diff --git a/src/clj_commons_exec.clj b/src/clj_commons_exec.clj index ced2257..71daba1 100644 --- a/src/clj_commons_exec.clj +++ b/src/clj_commons_exec.clj @@ -6,7 +6,9 @@ ByteArrayInputStream InputStream OutputStream - IOException] + IOException + PipedInputStream + PipedOutputStream] [org.apache.commons.exec CommandLine DefaultExecutor @@ -17,7 +19,8 @@ StreamPumper InputStreamPumper ShutdownHookProcessDestroyer - Watchdog] + Watchdog + PumpStreamHandler] [org.apache.commons.exec.environment EnvironmentUtils])) @@ -105,8 +108,8 @@ (try (.join t) (catch InterruptedException _))))))) -(defn string->input-stream [^String s ^String encode] - (ByteArrayInputStream. (.getBytes s (or encode (System/getProperty "file.encoding"))))) +(defn string->input-stream [^String s & encode] + (ByteArrayInputStream. (.getBytes s (or ^String encode (System/getProperty "file.encoding"))))) (defn sh [& args-and-opts] (let [[[^String comm & args] [opts]] (parse-args args-and-opts) @@ -144,3 +147,97 @@ (.execute executor command env result-handler)) (.execute executor command result-handler))) result)) + +(defn- command-line [cmd-and-args] + "create a CommandLine object given a list/vector that has the base command and all the CLI arguments/options" + (let [base-command (first cmd-and-args) + cl (CommandLine. base-command) + args-opts (rest cmd-and-args)] + (doseq [ao args-opts] + (doto cl + (.addArgument ao))) + cl)) + +(defn- run-pipe-2 [cmd-and-args1 cmd-and-args2] + "works for two commands, one piped to the second. each command a vector of strings. return a future that contains a map similar to the one contained in the sh function's return promise" + (let [result (promise)] + ;; putting result promise in outer let in case + ;; the inner let bindings can be GC'ed. this is premature optimization(?) + (let [cl1 (command-line cmd-and-args1) + cl2 (command-line cmd-and-args2) + pos (java.io.PipedOutputStream.) + pis (java.io.PipedInputStream. pos) + output (java.io.ByteArrayOutputStream.) + error (java.io.ByteArrayOutputStream.) + exec1 (doto (DefaultExecutor.) (.setStreamHandler (PumpStreamHandler. pos nil nil))) + exec2 (doto (DefaultExecutor.) (.setStreamHandler (PumpStreamHandler. output error pis))) + t1-fn (fn [] (.execute exec1 cl1)) + t2-fn (fn [] (deliver result + (future (do + (let [exit-code (.execute exec2 cl2)] + {:exit exit-code + :out (.toString output) + :err (.toString error)}))))) + t1 (Thread. t1-fn) + t2 (Thread. t2-fn)] + (.start t1) + (.start t2)) + @result)) + +(defn- run-in-str-1 [cmd-and-args in-str] + "feed the input string into the command. command specified as a vector of strings. return a future that contains a map similar to the one contained in the sh function's return promise" + (let [cl (command-line cmd-and-args) + output (java.io.ByteArrayOutputStream.) + error (java.io.ByteArrayOutputStream.) + input (string->input-stream (str in-str \newline)) + exec (doto (DefaultExecutor.) (.setStreamHandler (PumpStreamHandler. output error input)))] + (future (do + (let [exit-code (.execute exec cl)] + {:exit exit-code + :out (.toString output) + :err (.toString error)}))))) + +(defn run [cmd-and-args-list & [input]] + "run one or more commands, each command piped to the next if more than one. each command in the list specified as a vector of strings, the/all command string vector(s) enclosed inside one outer vector. return a future that contains a map similar to the one contained in the sh function's return promise" + (let [result (promise)] + ;; putting result promise in outer let in case + ;; the inner let bindings can be GC'ed. this is premature optimization(?) + (let [first-input (if (string? input) (string->input-stream (str input \newline)) input) + last-output (java.io.ByteArrayOutputStream.) + last-error (java.io.ByteArrayOutputStream.) + cmds (for [cmd-and-args cmd-and-args-list] + (command-line cmd-and-args)) + num-cmds (count cmds) + nil-streams (into [] + (for [_ (range num-cmds)] + [nil nil nil])) + pipe-streams (loop [streams nil-streams + i 0] + (if (>= i (dec num-cmds)) + streams + (let [pos (java.io.PipedOutputStream.) + pis (java.io.PipedInputStream. pos) + new-streams (-> streams + (assoc-in [i 0] pos) + (assoc-in [(inc i) 2] pis))] + (recur new-streams (inc i))))) + all-streams (-> pipe-streams + (assoc-in [0 2] first-input) + (assoc-in [(dec num-cmds) 0] last-output) + (assoc-in [(dec num-cmds) 1] last-error)) + execs (for [cmd-streams all-streams] + (doto (DefaultExecutor.) (.setStreamHandler (apply #(PumpStreamHandler. %1 %2 %3) cmd-streams)))) + butlast-thread-fns (map (fn [exec cmd] (fn [] (.execute exec cmd))) execs cmds) + last-thread-fn (fn [] (deliver result + (future (do + (let [exit-code (.execute (last execs) (last cmds))] + {:exit exit-code + :out (.toString last-output) + :err (.toString last-error)}))))) + all-thread-fns (concat butlast-thread-fns [last-thread-fn]) + threads (for [tfn all-thread-fns] + (Thread. tfn)) + ] + (doseq [t threads] + (.start t))) + @result)) \ No newline at end of file