parent
83b3aad920
commit
c2d9bbfab2
11 changed files with 121 additions and 233 deletions
26
README.md
26
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.
|
||||
|
|
|
|||
19
examples/process_builder.clj
Executable file
19
examples/process_builder.clj
Executable file
|
|
@ -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")
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
2
sci
2
sci
|
|
@ -1 +1 @@
|
|||
Subproject commit 07d28ee572e90a629e01b10aa5b98cb33ccdc1e5
|
||||
Subproject commit b86cb3db570ffcf6b193662481acceca25d3c979
|
||||
|
|
@ -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
|
||||
|
||||
)
|
||||
|
|
@ -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
|
||||
|
||||
)
|
||||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
@ -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)))
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
Loading…
Reference in a new issue