diff --git a/README.md b/README.md index 41656763..39eab12d 100644 --- a/README.md +++ b/README.md @@ -172,8 +172,6 @@ enumerated explicitly. - [`clojure.core.async`](https://clojure.github.io/core.async/) aliased as `async`. The `alt` and `go` macros are not available but `alts!!` does work as it is a function. -- [`me.raynes.conch.low-level`](https://github.com/clj-commons/conch#low-level-usage) - aliased as `conch` - [`clojure.tools.cli`](https://github.com/clojure/tools.cli) aliased as `tools.cli` - [`clojure.data.csv`](https://github.com/clojure/data.csv) aliased as `csv` - [`cheshire.core`](https://github.com/dakrone/cheshire) aliased as `json` @@ -191,6 +189,7 @@ The following Java classes are available: - `java.io.File` - `java.nio.Files` - `java.util.regex.Pattern` +- `ProcessBuilder` (see [example](examples/process_builder.clj)). - `String` - `System` - `Thread` @@ -446,22 +445,27 @@ A socket REPL client for Emacs is ## Spawning and killing a process -You may use the `conch` namespace for this. It maps to -[`me.raynes.conch.low-level`](https://github.com/clj-commons/conch#low-level-usage). +Use the `java.lang.ProcessBuilder` class. Example: ``` clojure -$ bb ' -(def ws (conch/proc "python" "-m" "SimpleHTTPServer" "1777")) -(net/wait-for-it "localhost" 1777) (conch/destroy ws)' +user=> (def ws (-> (ProcessBuilder. ["python" "-m" "SimpleHTTPServer" "1777"]) (.start))) +#'user/ws +user=> (wait/wait-for-port "localhost" 1777) +{:host "localhost", :port 1777, :took 2} +user=> (.destroy ws) +nil ``` +Also see this [example](examples/process_builder.clj). + ## Async -Apart from `future` for creating threads and the `conch` namespace for creating -processes, you may use the `async` namespace, which maps to `clojure.core.async`, for asynchronous scripting. The following -example shows how to get first available value from two different processes: +Apart from `future` and `pmap` for creating threads, you may use the `async` +namespace, which maps to `clojure.core.async`, for asynchronous scripting. The +following example shows how to get first available value from two different +processes: ``` clojure bb ' @@ -681,5 +685,3 @@ Distributed under the EPL License. See LICENSE. This project contains code from: - Clojure, which is licensed under the same EPL License. -- [conch](https://github.com/clj-commons/conch), which is licensed under the -same EPL License. diff --git a/examples/process_builder.clj b/examples/process_builder.clj new file mode 100755 index 00000000..0b064516 --- /dev/null +++ b/examples/process_builder.clj @@ -0,0 +1,19 @@ +#!/usr/bin/env bb + +(require '[clojure.java.io :as io]) +(import '[java.lang ProcessBuilder$Redirect]) + +(defn grep [input pattern] + (let [proc (-> (ProcessBuilder. ["grep" pattern]) + (.redirectOutput ProcessBuilder$Redirect/INHERIT) + (.redirectError ProcessBuilder$Redirect/INHERIT) + (.start)) + proc-input (.getOutputStream proc)] + (with-open [w (io/writer proc-input)] + (binding [*out* w] + (print input) + (flush))) + (.waitFor proc) + nil)) + +(grep "hello\nbye\n" "bye") diff --git a/reflection.json b/reflection.json index 63f3b320..3af3a5fc 100644 --- a/reflection.json +++ b/reflection.json @@ -23,6 +23,16 @@ "allPublicMethods" : true, "allPublicFields" : true, "allPublicConstructors" : true +}, { + "name" : "java.io.InputStream", + "allPublicMethods" : true, + "allPublicFields" : true, + "allPublicConstructors" : true +}, { + "name" : "java.io.OutputStream", + "allPublicMethods" : true, + "allPublicFields" : true, + "allPublicConstructors" : true }, { "name" : "java.io.StringReader", "allPublicMethods" : true, @@ -68,6 +78,21 @@ "allPublicMethods" : true, "allPublicFields" : true, "allPublicConstructors" : true +}, { + "name" : "java.lang.Process", + "allPublicMethods" : true, + "allPublicFields" : true, + "allPublicConstructors" : true +}, { + "name" : "java.lang.ProcessBuilder", + "allPublicMethods" : true, + "allPublicFields" : true, + "allPublicConstructors" : true +}, { + "name" : "java.lang.ProcessBuilder$Redirect", + "allPublicMethods" : true, + "allPublicFields" : true, + "allPublicConstructors" : true }, { "name" : "java.lang.String", "allPublicMethods" : true, @@ -78,6 +103,16 @@ "allPublicMethods" : true, "allPublicFields" : true, "allPublicConstructors" : true +}, { + "name" : "java.lang.UNIXProcess", + "allPublicMethods" : true, + "allPublicFields" : true, + "allPublicConstructors" : true +}, { + "name" : "java.lang.UNIXProcess$ProcessPipeOutputStream", + "allPublicMethods" : true, + "allPublicFields" : true, + "allPublicConstructors" : true }, { "name" : "java.nio.file.CopyOption", "allPublicMethods" : true, @@ -123,6 +158,11 @@ "allPublicMethods" : true, "allPublicFields" : true, "allPublicConstructors" : true +}, { + "name" : "java.util.concurrent.LinkedBlockingQueue", + "allPublicMethods" : true, + "allPublicFields" : true, + "allPublicConstructors" : true }, { "name" : "java.util.regex.Pattern", "allPublicMethods" : true, @@ -133,15 +173,6 @@ "allPublicMethods" : true, "allPublicFields" : true, "allPublicConstructors" : true -}, { - "allPublicMethods" : true, - "name" : "java.util.concurrent.LinkedBlockingQueue" -}, { - "allPublicConstructors" : true, - "name" : "java.lang.Process" -}, { - "allPublicMethods" : true, - "name" : "java.lang.UNIXProcess" }, { "methods" : [ { "name" : "activeCount" diff --git a/sci b/sci index 07d28ee5..b86cb3db 160000 --- a/sci +++ b/sci @@ -1 +1 @@ -Subproject commit 07d28ee572e90a629e01b10aa5b98cb33ccdc1e5 +Subproject commit b86cb3db570ffcf6b193662481acceca25d3c979 diff --git a/src/babashka/impl/Boolean.clj b/src/babashka/impl/Boolean.clj deleted file mode 100644 index ee2e1853..00000000 --- a/src/babashka/impl/Boolean.clj +++ /dev/null @@ -1,15 +0,0 @@ -(ns babashka.impl.Boolean - {:no-doc true} - (:refer-clojure :exclude [list])) - -(set! *warn-on-reflection* true) - -(defn parseBoolean [^String x] - (Boolean/parseBoolean x)) - -(def boolean-bindings - {'Boolean/parseBoolean parseBoolean}) - -(comment - - ) diff --git a/src/babashka/impl/Double.clj b/src/babashka/impl/Double.clj deleted file mode 100644 index 50882b12..00000000 --- a/src/babashka/impl/Double.clj +++ /dev/null @@ -1,15 +0,0 @@ -(ns babashka.impl.Double - {:no-doc true} - (:refer-clojure :exclude [list])) - -(set! *warn-on-reflection* true) - -(defn parseDouble [^String x] - (Double/parseDouble x)) - -(def double-bindings - {'Double/parseDouble parseDouble}) - -(comment - - ) diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj index 16736baf..e57a345e 100644 --- a/src/babashka/impl/classes.clj +++ b/src/babashka/impl/classes.clj @@ -4,40 +4,43 @@ [cheshire.core :as json])) (def classes - {:default-classes '[java.lang.ArithmeticException + {:default-classes '[clojure.lang.ExceptionInfo + clojure.lang.LineNumberingPushbackReader + java.io.BufferedReader + java.io.BufferedWriter + java.io.File + java.io.InputStream + java.io.OutputStream + java.io.StringReader + java.io.StringWriter + java.lang.ArithmeticException java.lang.AssertionError java.lang.Boolean - java.io.BufferedWriter - java.io.BufferedReader java.lang.Class java.lang.Double java.lang.Exception - clojure.lang.ExceptionInfo java.lang.Integer - java.io.File - clojure.lang.LineNumberingPushbackReader - java.util.regex.Pattern + java.util.concurrent.LinkedBlockingQueue java.lang.String - java.io.StringReader - java.io.StringWriter java.lang.System - sun.nio.fs.UnixPath - java.nio.file.attribute.FileAttribute - java.nio.file.attribute.PosixFilePermission - java.nio.file.attribute.PosixFilePermissions + java.lang.Process + java.lang.UNIXProcess + java.lang.UNIXProcess$ProcessPipeOutputStream + java.lang.ProcessBuilder + java.lang.ProcessBuilder$Redirect java.nio.file.CopyOption java.nio.file.FileAlreadyExistsException java.nio.file.Files java.nio.file.NoSuchFileException java.nio.file.Path - java.nio.file.StandardCopyOption] - :custom-classes {'java.util.concurrent.LinkedBlockingQueue ;; why? - {:allPublicMethods true} - 'java.lang.Process ;; for conch? - {:allPublicConstructors true} - 'java.lang.UNIXProcess ;; for conch? - {:allPublicMethods true} - 'java.lang.Thread + java.nio.file.StandardCopyOption + java.nio.file.attribute.FileAttribute + java.nio.file.attribute.PosixFilePermission + java.nio.file.attribute.PosixFilePermissions + java.util.regex.Pattern + sun.nio.fs.UnixPath ;; included because of permission check + ] + :custom-classes {'java.lang.Thread ;; generated with `public-declared-method-names`, see in ;; `comment` below {:methods [{:name "activeCount"} @@ -84,16 +87,21 @@ (def class-map (gen-class-map)) +#_(defn sym->class-name [sym] + (-> sym str (str/replace "$" "."))) + (defn generate-reflection-file "Generate reflection.json file" [& args] - (let [entries (vec (for [c (sort (:default-classes classes))] - {:name (str c) + (let [entries (vec (for [c (sort (:default-classes classes)) + :let [class-name (str c)]] + {:name class-name :allPublicMethods true :allPublicFields true :allPublicConstructors true})) - custom-entries (for [[k v] (:custom-classes classes)] - (assoc v :name (str k))) + custom-entries (for [[c v] (:custom-classes classes) + :let [class-name (str c)]] + (assoc v :name class-name)) all-entries (concat entries custom-entries)] (spit (or (first args) @@ -114,5 +122,5 @@ (sort-by :name) (vec))) - (public-declared-method-names java.lang.Thread) + (public-declared-method-names java.lang.UNIXProcess) ) diff --git a/src/babashka/impl/conch.clj b/src/babashka/impl/conch.clj deleted file mode 100644 index e53e8a99..00000000 --- a/src/babashka/impl/conch.clj +++ /dev/null @@ -1,18 +0,0 @@ -(ns babashka.impl.conch - {:no-doc true} - (:require - [babashka.impl.me.raynes.conch.low-level :as ll])) - -(def conch-namespace - {;; low level API - 'proc ll/proc - 'destroy ll/destroy - 'exit-code ll/exit-code - 'flush ll/flush - 'done ll/done - 'stream-to ll/stream-to - 'feed-from ll/feed-from - 'stream-to-string ll/stream-to-string - 'stream-to-out ll/stream-to-out - 'feed-from-string ll/feed-from-string - 'read-line ll/read-line}) diff --git a/src/babashka/impl/me/raynes/conch/low_level.clj b/src/babashka/impl/me/raynes/conch/low_level.clj deleted file mode 100644 index 0de31bdc..00000000 --- a/src/babashka/impl/me/raynes/conch/low_level.clj +++ /dev/null @@ -1,126 +0,0 @@ -;; From https://github.com/clj-commons/conch - -(ns babashka.impl.me.raynes.conch.low-level - "A simple but flexible library for shelling out from Clojure." - {:no-doc true} - (:refer-clojure :exclude [flush read-line]) - (:require [clojure.java.io :as io]) - (:import [java.util.concurrent TimeUnit TimeoutException] - [java.io InputStream OutputStream])) - -(set! *warn-on-reflection* true) - -(defn proc - "Spin off another process. Returns the process's input stream, - output stream, and err stream as a map of :in, :out, and :err keys - If passed the optional :dir and/or :env keyword options, the dir - and enviroment will be set to what you specify. If you pass - :verbose and it is true, commands will be printed. If it is set to - :very, environment variables passed, dir, and the command will be - printed. If passed the :clear-env keyword option, then the process - will not inherit its environment from its parent process." - [& args] - (let [[cmd args] (split-with (complement keyword?) args) - args (apply hash-map args) - builder (ProcessBuilder. ^"[Ljava.lang.String;" (into-array String cmd)) - env (.environment builder)] - (when (:clear-env args) - (.clear env)) - (doseq [[k v] (:env args)] - (.put env k v)) - (when-let [dir (:dir args)] - (.directory builder (io/file dir))) - (when (:verbose args) (apply println cmd)) - (when (= :very (:verbose args)) - (when-let [env (:env args)] (prn env)) - (when-let [dir (:dir args)] (prn dir))) - (when (:redirect-err args) - (.redirectErrorStream builder true)) - (let [process (.start builder)] - {:out (.getInputStream process) - :in (.getOutputStream process) - :err (.getErrorStream process) - :process process}))) - -(defn destroy - "Destroy a process." - [process] - (.destroy ^Process (:process process))) - -;; .waitFor returns the exit code. This makes this function useful for -;; both getting an exit code and stopping the thread until a process -;; terminates. -(defn exit-code - "Waits for the process to terminate (blocking the thread) and returns - the exit code. If timeout is passed, it is assumed to be milliseconds - to wait for the process to exit. If it does not exit in time, it is - killed (with or without fire)." - ([process] (.waitFor ^Process (:process process))) - ([process timeout] - (try - (let [^java.util.concurrent.Future fut - (future (.waitFor ^Process (:process process)))] - (.get fut timeout TimeUnit/MILLISECONDS)) - (catch Exception e - (if (or (instance? TimeoutException e) - (instance? TimeoutException (.getCause e))) - (do (destroy process) - :timeout) - (throw e)))))) - -(defn flush - "Flush the output stream of a process." - [process] - (let [^OutputStream in (:in process)] - (.flush in))) - -(defn done - "Close the process's output stream (sending EOF)." - [proc] - (let [^OutputStream in (:in proc)] - (.close in))) - -(defn stream-to - "Stream :out or :err from a process to an ouput stream. - Options passed are fed to clojure.java.io/copy. They are :encoding to - set the encoding and :buffer-size to set the size of the buffer. - :encoding defaults to UTF-8 and :buffer-size to 1024." - [process from to & args] - (apply io/copy (process from) to args)) - -(defn feed-from - "Feed to a process's input stream with optional. Options passed are - fed to clojure.java.io/copy. They are :encoding to set the encoding - and :buffer-size to set the size of the buffer. :encoding defaults to - UTF-8 and :buffer-size to 1024. If :flush is specified and is false, - the process will be flushed after writing." - [process from & {flush? :flush :or {flush? true} :as all}] - (apply io/copy from (:in process) all) - (when flush? (flush process))) - -(defn stream-to-string - "Streams the output of the process to a string and returns it." - [process from & args] - (with-open [writer (java.io.StringWriter.)] - (apply stream-to process from writer args) - (str writer))) - -;; The writer that Clojure wraps System/out in for *out* seems to buffer -;; things instead of writing them immediately. This wont work if you -;; really want to stream stuff, so we'll just skip it and throw our data -;; directly at System/out. -(defn stream-to-out - "Streams the output of the process to System/out" - [process from & args] - (apply stream-to process from (System/out) args)) - -(defn feed-from-string - "Feed the process some data from a string." - [process s & args] - (apply feed-from process (java.io.StringReader. s) args)) - -(defn read-line - "Read a line from a process' :out or :err." - [process from] - (binding [*in* (io/reader (from process))] - (clojure.core/read-line))) diff --git a/src/babashka/main.clj b/src/babashka/main.clj index 9e4f36c4..454c7309 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -3,18 +3,17 @@ (:require [babashka.impl.async :refer [async-namespace]] [babashka.impl.cheshire :refer [cheshire-core-namespace]] + [babashka.impl.classes :as classes] + [babashka.impl.classpath :as cp] [babashka.impl.clojure.core :refer [core-extras]] [babashka.impl.clojure.java.io :refer [io-namespace]] [babashka.impl.clojure.stacktrace :refer [print-stack-trace]] - [babashka.impl.conch :refer [conch-namespace]] [babashka.impl.csv :as csv] [babashka.impl.pipe-signal-handler :refer [handle-pipe! pipe-signal-received?]] [babashka.impl.repl :as repl] [babashka.impl.socket-repl :as socket-repl] [babashka.impl.tools.cli :refer [tools-cli-namespace]] [babashka.impl.utils :refer [eval-string]] - [babashka.impl.classpath :as cp] - [babashka.impl.classes :as classes] [babashka.wait :as wait] [clojure.edn :as edn] [clojure.java.io :as io] @@ -236,7 +235,6 @@ Everything after that is bound to *command-line-args*.")) sig babashka.signal shell clojure.java.shell io clojure.java.io - conch me.raynes.conch.low-level async clojure.core.async csv clojure.data.csv json cheshire.core} @@ -249,7 +247,6 @@ Everything after that is bound to *command-line-args*.")) 'wait-for-path wait/wait-for-path} 'babashka.signal {'pipe-signal-received? pipe-signal-received?} 'clojure.java.io io-namespace - 'me.raynes.conch.low-level conch-namespace 'clojure.core.async async-namespace 'clojure.data.csv csv/csv-namespace 'cheshire.core cheshire-core-namespace} @@ -266,6 +263,7 @@ Everything after that is bound to *command-line-args*.")) Exception java.lang.Exception Integer java.lang.Integer File java.io.File + ProcessBuilder java.lang.ProcessBuilder String java.lang.String System java.lang.System Thread java.lang.Thread} diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj index e1f2a224..2352645a 100644 --- a/test/babashka/main_test.clj +++ b/test/babashka/main_test.clj @@ -166,8 +166,12 @@ (deftest future-test (is (= 6 (bb nil "@(future (+ 1 2 3))")))) -(deftest conch-test - (is (str/includes? (bb nil "(->> (conch/proc \"ls\") (conch/stream-to-string :out))") +(deftest process-builder-test + (is (str/includes? (bb nil " +(def ls (-> (ProcessBuilder. [\"ls\"]) (.start))) +(def output (.getInputStream ls)) +(.waitFor ls) +(slurp output)") "LICENSE"))) (deftest create-temp-file-test @@ -182,10 +186,10 @@ (deftest wait-for-port-test (is (= :timed-out - (bb nil "(def web-server (conch/proc \"python\" \"-m\" \"SimpleHTTPServer\" \"7171\")) - (wait/wait-for-port \"127.0.0.1\" 7171) - (conch/destroy web-server) - (wait/wait-for-port \"localhost\" 7172 {:default :timed-out :timeout 50})")))) + (bb nil "(def ws (-> (ProcessBuilder. [\"python\" \"-m\" \"SimpleHTTPServer\" \"1777\"]) (.start))) + (wait/wait-for-port \"127.0.0.1\" 1777) + (.destroy ws) + (wait/wait-for-port \"localhost\" 1777 {:default :timed-out :timeout 50})")))) (deftest wait-for-path-test (let [temp-dir-path (System/getProperty "java.io.tmpdir")]