diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..11ecb79 --- /dev/null +++ b/LICENSE @@ -0,0 +1,198 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' from + a Contributor if it was added to the Program by such Contributor itself or + anyone acting on such Contributor's behalf. Contributions do not include + additions to the Program which: (i) are separate modules of software + distributed in conjunction with the Program under their own license + agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly perform, + distribute and sublicense the Contribution of such Contributor, if any, and + such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of the + Contribution and the Program if, at the time the Contribution is added by + the Contributor, such addition of the Contribution causes such combination + to be covered by the Licensed Patents. The patent license shall not apply + to any other combinations which include the Contribution. No hardware per + se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses to + its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other entity + based on infringement of intellectual property rights or otherwise. As a + condition to exercising the rights and licenses granted hereunder, each + Recipient hereby assumes sole responsibility to secure any other + intellectual property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to distribute the Program, it + is Recipient's responsibility to acquire that license before distributing + the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its +own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties and + conditions, express and implied, including warranties or conditions of + title and non-infringement, and implied warranties or conditions of + merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are offered + by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained within + the Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor to +control, and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may participate in +any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its exercise of +rights under this Agreement , including but not limited to the risks and costs +of program errors, compliance with applicable laws, damage to or loss of data, +programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted under +Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue and +survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to time. +No one other than the Agreement Steward has the right to modify this Agreement. +The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation +may assign the responsibility to serve as the Agreement Steward to a suitable +separate entity. Each new version of the Agreement will be given a +distinguishing version number. The Program (including Contributions) may always +be distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to distribute the Program (including its Contributions) +under the new version. Except as expressly stated in Sections 2(a) and 2(b) +above, Recipient receives no rights or licenses to the intellectual property of +any Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted under +this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. diff --git a/README.md b/README.md new file mode 100644 index 0000000..65749e3 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# babashka.pods + +A library to load babashka pods. Used by babashka but also usable from the JVM +or other [sci](https://github.com/borkdude/sci)-based projects. + +## Usage + +On the JVM: + +``` clojure +(require '[babashka.pods.jvm :as pods]) +(pods/load-pod "pod-babashka-hsqldb") +(require '[pod.babashka.hsqldb :as sql]) + +(def db "jdbc:hsqldb:mem:testdb;sql.syntax_mys=true") +(sql/execute! db ["create table foo ( foo int );"]) +;;=> [#:next.jdbc{:update-count 0}] +``` + +From the [Small Clojure Interpreter](https://github.com/borkdude/sci): + +See [test/babashka/pods/sci_test.clj](test/babashka/pods/sci_test.clj). + +## License + +Copyright © 2020 Michiel Borkent + +Distributed under the EPL License. See LICENSE. diff --git a/deps.edn b/deps.edn index ca1e9f6..8d1119f 100644 --- a/deps.edn +++ b/deps.edn @@ -1,4 +1,17 @@ -{:deps {borkdude/sci {:mvn/version "0.0.13-alpha.19"} - nrepl/bencode {:mvn/version "1.1.0"} +{:deps {nrepl/bencode {:mvn/version "1.1.0"} cheshire {:mvn/version "5.10.0"} - org.clojure/core.async {:mvn/version "1.1.587"}}} + org.clojure/core.async {:mvn/version "1.1.587"}} + :aliases + {:sci + {:extra-deps + {borkdude/sci {:mvn/version "0.0.13-alpha.19"}}} + :test + {:extra-deps + {test-runner + {:git/url "https://github.com/cognitect-labs/test-runner" + :sha "cb96e80f6f3d3b307c59cbeb49bb0dcb3a2a780b"}} + :extra-paths ["test"] + :main-opts ["-m" "cognitect.test-runner"]} + :test-pod + {:paths ["src" "test-pod"] + :main-opts ["-m" "pod.test-pod"]}}} diff --git a/script/test b/script/test new file mode 100755 index 0000000..406ba73 --- /dev/null +++ b/script/test @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +clj -A:test -n babashka.pods.jvm-test +clj -A:sci:test -n babashka.pods.sci-test diff --git a/src/babashka/pods/impl.clj b/src/babashka/pods/impl.clj index 058d6d4..5846367 100644 --- a/src/babashka/pods/impl.clj +++ b/src/babashka/pods/impl.clj @@ -4,8 +4,7 @@ (:require [bencode.core :as bencode] [cheshire.core :as cheshire] [clojure.core.async :as async] - [clojure.edn :as edn] - [sci.core :as sci])) + [clojure.edn :as edn])) (set! *warn-on-reflection* true) @@ -32,6 +31,8 @@ (let [stdout (:stdout pod) format (:format pod) chans (:chans pod) + out-stream (:out pod) + err-stream (:err pod) read-fn (case format :edn edn/read-string :json #(cheshire/parse-string-strict % true))] @@ -66,13 +67,14 @@ bytes->string)] (when (or value* error?) (async/put! chan value)) (when (or done? error?) (async/close! chan)) - (when out (binding [*out* @sci/out] - (println out))) - (when err (binding [*out* @sci/err] + (when out + (binding [*out* out-stream] + (println out))) + (when err (binding [*out* err-stream] (println err)))) (recur)) (catch Exception e - (binding [*out* @sci/err] + (binding [*out* err-stream] (prn e)))))) (defn next-id [] @@ -119,7 +121,9 @@ :stdout stdout :chans (atom {}) :format format - :ops ops} + :ops ops + :out *out* + :err *err*} _ (add-shutdown-hook! (fn [] (if (contains? ops :shutdown) diff --git a/src/babashka/pods/jvm.clj b/src/babashka/pods/jvm.clj index c6ca85d..aeb621f 100644 --- a/src/babashka/pods/jvm.clj +++ b/src/babashka/pods/jvm.clj @@ -7,7 +7,7 @@ (let [pod (impl/load-pod pod-spec _opts) namespaces (:namespaces pod)] (doseq [[ns-sym v] namespaces] - (create-ns ns-sym) + (load-string (format "(ns %s)" ns-sym)) (doseq [[var-sym v] v] (intern ns-sym var-sym v))) (future (impl/processor pod))))) diff --git a/src/babashka/pods/sci.clj b/src/babashka/pods/sci.clj index ea12fb7..4ee557e 100644 --- a/src/babashka/pods/sci.clj +++ b/src/babashka/pods/sci.clj @@ -2,14 +2,16 @@ (:require [babashka.pods.impl :as impl] [sci.core :as sci])) -(defn load-pod - ([ctx pod-spec] (load-pod ctx pod-spec nil)) - ([ctx pod-spec _opts] - (let [pod (impl/load-pod pod-spec _opts) - namespaces (:namespaces pod) - env (:env ctx)] - (swap! env - (fn [env] - update env :namespaces merge namespaces)) - (sci/future (impl/processor pod))))) - +(def load-pod + (with-meta + (fn + ([ctx pod-spec] (load-pod ctx pod-spec nil)) + ([ctx pod-spec _opts] + (let [pod (binding [*out* @sci/out + *err* @sci/err] + (impl/load-pod pod-spec _opts)) + namespaces (:namespaces pod) + env (:env ctx)] + (swap! env update :namespaces merge namespaces) + (sci/future (impl/processor pod))))) + {:sci.impl/op :needs-ctx})) diff --git a/test-pod/pod/test_pod.clj b/test-pod/pod/test_pod.clj new file mode 100644 index 0000000..17efda6 --- /dev/null +++ b/test-pod/pod/test_pod.clj @@ -0,0 +1,115 @@ +(ns pod.test-pod + (:refer-clojure :exclude [read read-string]) + (:require [bencode.core :as bencode] + [cheshire.core :as cheshire] + [clojure.edn :as edn] + [clojure.java.io :as io]) + (:import [java.io PushbackInputStream]) + (:gen-class)) + +(def debug? false) + +(defn debug [& args] + (when debug? + (binding [*out* (io/writer "/tmp/log.txt" :append true)] + (apply println args)))) + +(def stdin (PushbackInputStream. System/in)) + +(defn write [v] + (bencode/write-bencode System/out v) + (.flush System/out)) + +(defn read-string [^"[B" v] + (String. v)) + +(defn read [] + (bencode/read-bencode stdin)) + +(defn run-pod [cli-args] + (let [format (if (contains? cli-args "--json") + :json + :edn) + write-fn (if (identical? :json format) + cheshire/generate-string + pr-str) + read-fn (if (identical? :json format) + #(cheshire/parse-string % true) + edn/read-string)] + (loop [] + (let [message (try (read) + (catch java.io.EOFException _ + ::EOF))] + (when-not (identical? ::EOF message) + (let [op (get message "op") + op (read-string op) + op (keyword op)] + (case op + :describe (do (write {"format" (if (= format :json) + "json" + "edn") + "namespaces" + [{"name" "pod.test-pod" + "vars" [{"name" "add-sync"} + {"name" "range-stream" + "async" "true"} + {"name" "assoc"} + {"name" "error"} + {"name" "print"} + {"name" "print-err"}]}] + "ops" {"shutdown" {}}}) + (recur)) + :invoke (let [var (-> (get message "var") + read-string + symbol) + _ (debug "var" var) + id (-> (get message "id") + read-string) + args (get message "args") + args (read-string args) + args (read-fn args)] + (case var + pod.test-pod/add-sync (write + {"value" (write-fn (apply + args)) + "id" id + "status" ["done"]}) + pod.test-pod/range-stream + (let [rng (apply range args)] + (doseq [v rng] + (write + {"value" (write-fn v) + "id" id}) + (Thread/sleep 100)) + (write + {"status" ["done"] + "id" id})) + pod.test-pod/assoc + (write + {"value" (write-fn (apply assoc args)) + "status" ["done"] + "id" id}) + pod.test-pod/error + (write + {"ex-data" (write-fn {:args args}) + "ex-message" (str "Illegal arguments") + "status" ["done" "error"] + "id" id}) + pod.test-pod/print + (do (write + {"out" (pr-str args) + "id" id}) + (write + {"status" ["done"] + "id" id})) + pod.test-pod/print-err + (do (write + {"err" (pr-str args) + "id" id}) + (write + {"status" ["done"] + "id" id}))) + (recur)) + :shutdown (System/exit 0)))))))) + +(defn -main [& args] + (run-pod (set args))) diff --git a/test/babashka/pods/jvm_test.clj b/test/babashka/pods/jvm_test.clj new file mode 100644 index 0000000..4090d72 --- /dev/null +++ b/test/babashka/pods/jvm_test.clj @@ -0,0 +1,41 @@ +(ns babashka.pods.jvm-test + (:require [clojure.test :refer [deftest is]])) + +(deftest jvm-test2 + (let [out (java.io.StringWriter.) + err (java.io.StringWriter.) + ret (binding [*out* out + *err* err] + (try (load-string + " +(require '[babashka.pods.jvm :as pods]) + +(require '[clojure.core.async :as async]) + +(pods/load-pod [\"clojure\" \"-A:test-pod\" \"--run-as-pod\"]) + +(require '[pod.test-pod :as pod]) +(def stream-results (atom [])) +(let [chan (pod.test-pod/range-stream 1 10)] + (loop [] + (when-let [x (async/