Compare commits

...

47 commits

Author SHA1 Message Date
Ingy döt Net
47e55fe5e7
Fix typo (#71) 2024-05-08 14:17:43 +02:00
Michiel Borkent
717cef7af5 ignore 2024-02-27 11:52:37 +01:00
Jo Geraerts
cd968459a7
correct small typo in README.md (#69) 2024-01-26 13:26:09 +01:00
Flávio Sousa
8b717eb001
Update README.md with BABASHKA_PODS_DIR (#68) 2023-06-05 11:13:35 +02:00
Michiel Borkent
6ad6045b94 minor 2023-05-12 16:42:25 +02:00
Jude Payne
b00133ca05
Fix #66: opt-in metadata (#67) 2023-05-12 16:09:40 +02:00
Michiel Borkent
64ecb94de8 changelog 2023-04-02 11:27:07 +02:00
Michiel Borkent
d29cf6aa65 Changelog 2023-04-02 11:26:24 +02:00
Michiel Borkent
1635931483 Fix #65, fix warnings when defining var with core name 2023-04-02 11:25:51 +02:00
Michiel Borkent
75c2216649 Changelog 2023-01-09 21:21:45 +01:00
Michiel Borkent
c2e3d8f8b8 Fix #59: delete port file on exit 2023-01-09 21:21:34 +01:00
Michiel Borkent
16bea5b7db Undo verbose 2023-01-09 21:19:04 +01:00
Michiel Borkent
85c554e643 Fix #63: create directory before un-tarring 2023-01-09 21:16:53 +01:00
Michiel Borkent
4fb0da7daf changelog script 2022-12-29 17:10:04 +01:00
Michiel Borkent
76313a7089 changelog script 2022-12-29 17:07:51 +01:00
Michiel Borkent
28cf3de4ef v0.2.0 2022-12-29 17:06:37 +01:00
Michiel Borkent
81f8845d72
Fix #60 and fix #61, see changelog (#62) 2022-12-29 17:05:23 +01:00
Nate Jones
decf791000
Switch "out" and "err" messages to print and flush instead of println (#57) 2022-09-29 09:27:03 +02:00
Crispin Wellington
53f79da09d
set TCP_NODELAY on transport socket (#55) 2022-09-12 12:43:15 +02:00
Michiel Borkent
8bc0852799
Revert "Revert "Allow env vars OS_NAME & OS_ARCH to override os props (#53)"" (#54)
This reverts commit 6cbbdd118d.
2022-09-07 21:23:32 +02:00
Michiel Borkent
6cbbdd118d Revert "Allow env vars OS_NAME & OS_ARCH to override os props (#53)"
This reverts commit 5754d433aa.
2022-09-07 20:02:27 +02:00
Wes Morgan
5754d433aa
Allow env vars OS_NAME & OS_ARCH to override os props (#53) 2022-09-07 19:47:38 +02:00
Randy Stauner
6df443fabf
Correct spelling of async invocation keys (#51) 2022-07-16 21:00:42 +02:00
Michiel Borkent
93081b75e6
Manifest dir with env var (#50) 2022-07-04 19:57:35 +02:00
Michiel Borkent
66867eee7f CHANGELOG 2022-05-30 10:11:03 +02:00
Michiel Borkent
eb0b01c0a6 fix #49: don't log socket closed exception 2022-05-30 10:10:12 +02:00
Wes Morgan
b9f3a28555
Add babashka/fs to project.clj and deps.edn (#48)
Now that we're using it in the pods.impl ns
2022-05-14 19:28:07 +02:00
Wes Morgan
aee5337cef
Check that cache dir is writable before writing (#47)
...pod metadata cache file
2022-05-14 18:44:00 +02:00
Michiel Borkent
c2d6ba847c pods env var 2022-05-06 14:53:31 +02:00
Michiel Borkent
e075b13bfe
Add fallback for Apple M1 (#46) 2022-03-23 19:32:47 +01:00
Wes Morgan
842ff34739
Fix tests, remove accidental babashka code dependency, & add CI config (#45)
* Update clojure CLI flags in script/test

* Throw error when not one of version or path

...with qualified symbol pod-spec

* Fix some minor formatting issues

* Check for new error message in pod-registry test

* Add a test for resolve fn in edn data readers

* Add CI config

* Try using clojure tools-deps image in CI

* Check for new error message in sci pod-registry test

* Use latest version of buddy in pod-registry test

...for more platform support

* Stop depending on babashka

Instead accept an arg for the thing we were getting from it (location of the bb.edn file)

* Save maven deps in cache in CI
2022-03-23 16:57:56 +01:00
Wes Morgan
f2cfdff899
Feature: declarative pods (#44)
* Use non-deprecated string->int method

* Remove unused next-pod-id

* Support declarative pods loaded on require

* Wait for pod shutdown in load-pod-metadata

* Type hint a File return value to avoid reflection

* Return pod metadata instead of putting in ctx

* Fix local pod loading & support :cache opt

* Document :pods in bb.edn

* Cache local pods metadata in project .babashka dir

* Pass pod resolve-fn to describe->metadata

Not only was this just a bug, but the accidental reference to clojure.core/resolve ballooned the final bb image size to >110MB!
2022-03-23 12:26:58 +01:00
Michiel Borkent
5fbf1d7b04 Revert "Feature: declarative pods loaded at require time (#43)"
This reverts commit 8f059da7e6.
2022-03-22 14:00:24 +01:00
Wes Morgan
8f059da7e6
Feature: declarative pods loaded at require time (#43)
* Use non-deprecated string->int method

* Remove unused next-pod-id

* Support declarative pods loaded on require

* Wait for pod shutdown in load-pod-metadata

* Type hint a File return value to avoid reflection

* Return pod metadata instead of putting in ctx

* Fix local pod loading & support :cache opt

* Document :pods in bb.edn

* Cache local pods metadata in project .babashka dir
2022-03-21 20:46:57 +01:00
Michiel Borkent
538fc6f414 v0.1.0 2021-12-19 20:25:20 +01:00
Paula Gearon
f360afa613
Readme update for metadata (#41) 2021-10-15 19:42:50 +02:00
Paula Gearon
973c4e5c9e
[#38] Support metadata on vars 2021-10-15 17:43:12 +02:00
Paula Gearon
401aeecf52
Fixed example (#39)
Fixed minor error in example
2021-10-13 13:15:05 +02:00
Michiel Borkent
de4c3610c9 minor 2021-05-19 23:48:14 +02:00
Michiel Borkent
0f31e57977 Rename 2021-05-19 21:31:51 +02:00
Michiel Borkent
37326045aa Add support for default transit handler #36 2021-05-19 21:19:18 +02:00
Michiel Borkent
6214f06146 Implicit pod-id 2021-05-19 20:34:38 +02:00
Michiel Borkent
05ecf97127 Scope transit read and write handlers to pod ids 2021-05-19 17:20:55 +02:00
Michiel Borkent
1fdd8231bd
[#33] Allow pods to register transit handlers for reading and writing 2021-05-17 11:41:51 +02:00
Huahai Yang
82aa362710
correct clojars badge url (#32) 2021-03-19 18:43:16 +01:00
Michiel Borkent
490073f93a Add clojars badge 2021-03-17 19:55:39 +01:00
Michiel Borkent
39241ebc82 deps 2021-03-17 19:53:42 +01:00
21 changed files with 870 additions and 310 deletions

28
.circleci/config.yml Normal file
View file

@ -0,0 +1,28 @@
version: 2.1
jobs:
test:
docker:
- image: clojure:openjdk-11-tools-deps-1.10.3.1087-slim-bullseye
working_directory: ~/repo
environment:
LEIN_ROOT: "true"
BABASHKA_PLATFORM: linux
resource_class: large
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "deps.edn" }}
# fallback to using latest cache if no exact match is found
- v1-dependencies-
- run: |
script/test
- save_cache:
paths:
- ~/.m2
key: v1-dependencies-{{ checksum "deps.edn" }}
workflows:
version: 2
ci:
jobs:
- test

4
.gitignore vendored
View file

@ -3,3 +3,7 @@
.lein-failures
/pom.xml
.lein-repl-history
.cache
.clj-kondo/babashka
.clj-kondo/rewrite-clj
src/scratch.clj

21
CHANGELOG.md Normal file
View file

@ -0,0 +1,21 @@
# Changelog
## Unreleased
- [#63](https://github.com/babashka/pods/issues/63): create directory before un-tarring
- [#59](https://github.com/babashka/pods/issues/59): delete port file on exit
- [#65](https://github.com/babashka/pods/issues/65): fix warnings when defining var with core name in JVM
- [#66](https://github.com/babashka/pods/issues/66): Allow metadata on fn arguments for transit+json
## v0.2.0
- [#61](https://github.com/babashka/pods/issues/61): add transit as explicit JVM dependency
- [#60](https://github.com/babashka/pods/issues/60): transform pod reader error into exception of caller
- Switch "out" and "err" messages to print and flush instead of `println` ([@justone](https://github.com/justone))
- Set TCP_NODELAY on transport socket ([@retrogradeorbit](https://github.com/retrogradeorbit))
- Allow env vars OS_NAME & OS_ARCH to override os props ([@cap10morgan](https://github.com/cap10morgan))
- [#49](https://github.com/babashka/pods/issues/49): don't log socket closed exception
## v0.1.0
Initial version

104
README.md
View file

@ -1,4 +1,6 @@
# babashka.pods
# Babashka pods
[![Clojars Project](https://img.shields.io/clojars/v/babashka/babashka.pods.svg)](https://clojars.org/babashka/babashka.pods)
Babashka pods are programs that can be used as Clojure libraries by babashka.
@ -72,7 +74,44 @@ On the JVM:
### Where does the pod come from?
When calling `load-pod` with a string or vector of strings, the pod is looked up on the local file system (either using the PATH, or using an absolute path). When it is called with a qualified symbol and a version - like `(load-pod 'org.babashka/aws "0.0.5")` then it will be looked up in and downloaded from the [pod-registry](https://github.com/babashka/pod-registry).
When calling `load-pod` with a string or vector of strings (or declaring it in your `bb.edn`),
the pod is looked up on the local file system (either using the PATH, or using an absolute path).
When it is called with a qualified symbol and a version - like `(load-pod 'org.babashka/aws "0.0.5")`
then it will be looked up in and downloaded from the [pod-registry](https://github.com/babashka/pod-registry). You can customize the file system location that `load-pod` will use by setting the `BABASHKA_PODS_DIR` environment variable.
By default babashka will search for a pod binary matching your system's OS and arch. If you want to download
pods for a different OS / arch (e.g. for deployment to servers), you can set one or both of the following
environment variables:
- `BABASHKA_PODS_OS_NAME=Linux` (or `Mac OS X` or any other value returned by Java's `os.name` property)
- `BABASHKA_PODS_OS_ARCH=aarch64` (or `amd64` or any other value returned by Java's `os.arch` property)
### In a babashka project
As of babashka 0.8.0 you can declare the pods your babashka project uses in your `bb.edn` file like so:
```clojure
:pods {org.babashka/hsqldb {:version "0.1.0"} ; will be downloaded from the babashka pod registry
my.local/pod {:path "../pod-my-local/my-pod-binary"
:cache false}} ; optionally disable namespace caching if you're actively working on this pod
```
Then you can just require the pods in your code like any other clojure lib:
```clojure
(ns my.project
(:require [pod.babashka.hsqldb :as sql]
[my.local.pod :as my-pod]))
(def db "jdbc:hsqldb:mem:testdb;sql.syntax_mys=true")
(sql/execute! db ["create table foo ( foo int );"])
;;=> [#:next.jdbc{:update-count 0}]
(my-pod/do-a-thing "foo")
;;=> "something"
```
The pods will then be loaded on demand when you require them. No need to call `load-pod` explicitly.
## Sci
@ -91,7 +130,7 @@ light weight replacement for native interop (JNI, JNA, etc.).
### Examples
Beyond the already available pods mentioned above, eductional examples of pods
Beyond the already available pods mentioned above, educational examples of pods
can be found [here](examples):
- [pod-lispyclouds-sqlite](examples/pod-lispyclouds-sqlite): a pod that
@ -189,7 +228,7 @@ JSON. It also declares that the pod exposes one namespace,
To encode payloads in EDN use `"edn"` and for Transit JSON use `"transit+json"`.
The pod encodes the above map to bencode and writes it to stdoud. The pod client
The pod encodes the above map to bencode and writes it to stdout. The pod client
reads this message from the pod's stdout.
Upon receiving this message, the pod client creates these namespaces and vars.
@ -335,6 +374,55 @@ In the pod client:
nil
```
#### Metadata
**From pod to pod client**
*Fixed Metadata on vars*
Pods may attach metadata to functions and macros by sending data to the pod client
in a `"meta"` field as part of a `"var"` section. The metadata must be an appropriate
map, encoded as an EDN string. This is only applicable to vars in the pod and will be
ignored if the var refers to Client-side code, since metadata can already be defined
in those code blocks (see 'Dynamic Metadata' below to enable the encoding of metadata).
For example, a pod can define a function called `add`:
``` clojure
{"format" "json"
"namespaces"
[{"name" "pod.babashka.demo"
"vars" [{"name" "add"
"meta" "{:doc \"arithmetic addition of 2 arguments\" :arglists ([a b])}"}]}]}
```
*Dynamic Metadata*
Pods may send metadata on values returned to the client if metadata encoding is enabled
for the particular transport format used by the pod.
For example, if your pod uses `:transit+json` as its format, you can enable metadata
encoding by adding `:transform transit/write-meta` (or whatever transit is aliased to)
to the optional map passed to `transit/writer`. e.g.:
````clojure
(transit/writer baos :json {:transform transit/write-meta})
````
##### From pod client to pod
Currently sending metadata on arguments passed to a pod function is available only for the
`transit+json` format and can be enabled on a per var basis.
A pod can enable metadata to be read on arguments by sending the "arg-meta" field to "true"
for the var representing that function. For example:
````clojure
{:format :transit+json
:namespaces [{:name "pod.babashka.demo"
:vars [{"name" "round-trip" "arg-meta" "true"}]}]}
````
#### Deferred namespace loading
When your pod exposes multiple namespaces that can be used independently from
@ -409,7 +497,7 @@ The arguments to `babashka.pods/invoke` are:
- a pod identifier string derived from the first described namespace.
- the symbol of the var to invoke
- the arguments to the var
- an opts map containing `:handler` containing callback functions: `:success`, `:error` and `:done`
- an opts map containing `:handlers` containing callback functions: `:success`, `:error` and `:done`
The return value of `babashka.pods/invoke` is a map containing `:result`. When
not using callbacks, this is the return value from the pod var invocation. When
@ -435,9 +523,9 @@ callback is only called if no errors were sent by the pod.
In the above example the wrapper function calls the pod identified by
`"pod.babashka.filewatcher"`. It calls the var
`pod.babashka.filewatcher/watch*`. In `:on-success` it pulls out received
`pod.babashka.filewatcher/watch*`. In `:success` it pulls out received
values, passing them to the user-provided callback. Additionally, it prints any
errors received from the pod library in `:on-error` to `*err*`.
errors received from the pod library in `:error` to `*err*`.
A user will then use `pod.babashka.filewatcher/watch` like this:
@ -454,7 +542,7 @@ user=> (fw/watch "/tmp" (fn [result] (prn "result" result)))
nil
user=> (spit "/tmp/foobar123.txt" "foo")
nil
user=> "result" {:path "/private/tmp/foobar123.txt", :type "create"}
user=> "result" {:path "/private/tmp/foobar123.txt", :type :create}
```
## Run tests

2
bb.edn Normal file
View file

@ -0,0 +1,2 @@
{:tasks {test {:doc "Run tests"
:task (shell "script/test")}}}

View file

@ -1,6 +1,7 @@
{:deps {nrepl/bencode {:mvn/version "1.1.0"}
cheshire/cheshire {:mvn/version "5.10.0"}
com.cognitect/transit-clj {:mvn/version "1.0.324"}}
com.cognitect/transit-clj {:mvn/version "1.0.324"}
babashka/fs {:mvn/version "0.1.6"}}
:aliases
{:sci
{:extra-deps
@ -18,5 +19,5 @@
{lambdaisland/kaocha {:mvn/version "1.0.632"}}
:main-opts ["-m" "kaocha.runner"]}
:test-pod
{:paths ["src" "test-pod"]
{:extra-paths ["src" "test-pod"]
:main-opts ["-m" "pod.test-pod"]}}}

View file

@ -1,15 +1,17 @@
(defproject babashka/babashka.pods "0.0.1"
(defproject babashka/babashka.pods "0.2.0"
:description "babashka pods"
:url "https://github.com/babashka/babashka.pods"
:scm {:name "git"
:url "https://github.com/babashka/babashka.pods"}
:license {:name "EPL-1.0"
:url "https://www.eclipse.org/legal/epl-1.0/"}
:dependencies [[org.clojure/clojure "1.10.2-alpha1"]
:dependencies [[org.clojure/clojure "1.10.3"]
[nrepl/bencode "1.1.0"]
[cheshire "5.10.0"]]
[cheshire "5.10.0"]
[babashka/fs "0.1.6"]
[com.cognitect/transit-clj "1.0.329"]]
:deploy-repositories [["clojars" {:url "https://clojars.org/repo"
:username :env/babashka_nrepl_clojars_user
:password :env/babashka_nrepl_clojars_pass
:username :env/clojars_user
:password :env/clojars_pass
:sign-releases false}]]
:profiles {:test {:dependencies [[borkdude/sci "0.0.13-alpha.27"]]}})
:profiles {:test {:dependencies [[borkdude/sci "0.2.4"]]}})

17
script/changelog.clj Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env bb
(ns changelog
(:require [clojure.string :as str]))
(let [changelog (slurp "CHANGELOG.md")
replaced (str/replace changelog
#" #(\d+)"
(fn [[_ issue after]]
(format " [#%s](https://github.com/babashka/pods/issues/%s)%s"
issue issue (str after))))
replaced (str/replace replaced
#"@([a-zA-Z0-9-_]+)([, \.)])"
(fn [[_ name after]]
(format "[@%s](https://github.com/%s)%s"
name name after)))]
(spit "CHANGELOG.md" replaced))

View file

@ -1,29 +1,32 @@
#!/usr/bin/env bash
set -eou pipefail
export BABASHKA_POD_TEST_FORMAT
export BABASHKA_POD_TEST_SOCKET
# format = edn
BABASHKA_POD_TEST_FORMAT=edn
echo "Testing edn"
clojure -A:test -n babashka.pods.jvm-test
clojure -A:sci:test -n babashka.pods.sci-test
clojure -M:test -n babashka.pods.jvm-test
clojure -M:sci:test -n babashka.pods.sci-test
clojure -M:test -n babashka.pods.impl-test
# format = json
BABASHKA_POD_TEST_FORMAT=json
echo "Testing json"
clojure -A:test -n babashka.pods.jvm-test
clojure -A:sci:test -n babashka.pods.sci-test
clojure -M:test -n babashka.pods.jvm-test
clojure -M:sci:test -n babashka.pods.sci-test
# format = json
BABASHKA_POD_TEST_FORMAT="transit+json"
echo "Testing transit"
clojure -A:test -n babashka.pods.jvm-test
clojure -A:sci:test -n babashka.pods.sci-test
clojure -M:test -n babashka.pods.jvm-test
clojure -M:sci:test -n babashka.pods.sci-test
# socket = true
unset BABASHKA_POD_TEST_FORMAT
BABASHKA_POD_TEST_SOCKET=true
echo "Testing socket"
clojure -A:test -n babashka.pods.jvm-test
clojure -A:sci:test -n babashka.pods.sci-test
clojure -M:test -n babashka.pods.jvm-test
clojure -M:sci:test -n babashka.pods.sci-test

View file

@ -14,3 +14,18 @@
(defn invoke
([pod-id-or-pod sym args] (invoke pod-id-or-pod sym args {}))
([pod-id-or-pod sym args opts] (jvm/invoke pod-id-or-pod sym args opts)))
(defmacro copy-var [name var]
`(do (def ~name ~var)
(let [m# (meta (var ~var))
doc# (:doc m#)
arglists# (:arglists m#)]
(alter-meta! (var ~name) assoc
:arglists arglists#
:doc doc#))))
#_:clj-kondo/ignore
(do
(copy-var add-transit-read-handler! jvm/add-transit-read-handler!)
(copy-var add-transit-write-handler! jvm/add-transit-write-handler!)
(copy-var set-default-transit-write-handler! jvm/set-default-transit-write-handler!))

View file

@ -28,6 +28,9 @@
(defn bytes->string [^"[B" bytes]
(String. bytes))
(defn bytes->boolean [^"[B" bytes]
(= "true" (String. bytes)))
(defn get-string [m k]
(-> (get m k)
bytes->string))
@ -36,17 +39,63 @@
(some-> (get m k)
bytes->string))
(defn get-maybe-boolean [m k]
(some-> (get m k)
bytes->boolean))
(defn next-id []
(str (java.util.UUID/randomUUID)))
(defn transit-json-read [^String s]
(def ^:dynamic *pod-id* nil)
(defonce transit-read-handlers (atom {}))
(defonce transit-read-handler-maps (atom {}))
(defn update-transit-read-handler-map []
(swap! transit-read-handler-maps assoc *pod-id*
(transit/read-handler-map (get @transit-read-handlers *pod-id*))))
(defn transit-json-read [pod-id ^String s]
(with-open [bais (java.io.ByteArrayInputStream. (.getBytes s "UTF-8"))]
(let [r (transit/reader bais :json)]
(let [r (transit/reader bais :json {:handlers (get @transit-read-handler-maps pod-id)})]
(transit/read r))))
(defn transit-json-write [^String s]
;; https://www.cognitect.com/blog/2015/9/10/extending-transit
(defn add-transit-read-handler!
([tag fn]
(let [rh (transit/read-handler fn)]
(swap! transit-read-handlers assoc-in [*pod-id* tag] rh)
(update-transit-read-handler-map)
nil)))
(defonce transit-write-handlers (atom {}))
(defonce transit-write-handler-maps (atom {}))
(defn update-transit-write-handler-map []
(swap! transit-write-handler-maps assoc *pod-id*
(transit/write-handler-map (get @transit-write-handlers *pod-id*))))
;; https://www.cognitect.com/blog/2015/9/10/extending-transit
(defn add-transit-write-handler!
[classes tag fn]
(let [rh (transit/write-handler tag fn)]
(doseq [class classes]
(swap! transit-write-handlers assoc-in [*pod-id* class] rh)))
(update-transit-write-handler-map)
nil)
(defonce transit-default-write-handlers (atom {}))
(defn set-default-transit-write-handler! [tag-fn val-fn]
(let [wh (transit/write-handler tag-fn val-fn)]
(swap! transit-default-write-handlers assoc *pod-id* wh)))
(defn transit-json-write
[pod-id ^String s metadata?]
(with-open [baos (java.io.ByteArrayOutputStream. 4096)]
(let [w (transit/writer baos :json)]
(let [w (transit/writer baos :json (cond-> {:handlers (get @transit-write-handler-maps pod-id)
:default-handler (get @transit-default-write-handlers pod-id)}
metadata? (assoc :transform transit/write-meta)))]
(transit/write w s)
(str baos))))
@ -58,7 +107,7 @@
write-fn (case format
:edn pr-str
:json cheshire/generate-string
:transit+json transit-json-write)
:transit+json #(transit-json-write (:pod-id pod) % (:arg-meta opts)))
id (next-id)
chan (if handlers handlers
(promise))
@ -82,11 +131,18 @@
#(Boolean/parseBoolean %))
name-sym (symbol name)
sym (symbol ns-name-str name)
code (get-maybe-string var "code")]
code (get-maybe-string var "code")
vmeta (some-> (get var "meta")
bytes->string
edn/read-string)
name-sym (if vmeta
(with-meta name-sym vmeta)
name-sym)
metadata? (get-maybe-boolean var "arg-meta")]
[name-sym
(or code
(fn [& args]
(let [res (invoke pod sym args {:async async?})]
(let [res (invoke pod sym args {:async async? :arg-meta metadata?})]
res)))]))
vars))
@ -112,28 +168,36 @@
(throw e)))))
:transit+json
(fn [s]
(try (transit-json-read s)
(try (transit-json-read (:pod-id pod) s)
(catch Exception e
(binding [*out* *err*]
(println "Cannot read Transit JSON: " (pr-str s))
(throw e))))))]
(binding [*pod-id* (:pod-id pod)]
(try
(loop []
(let [reply (try (read stdout)
(catch java.io.EOFException _
::EOF))]
::EOF)
(catch java.net.SocketException e
(if (= "Socket closed" (ex-message e))
::EOF
(throw e))))]
(when-not (identical? ::EOF reply)
(let [id (get reply "id")
id (bytes->string id)
value* (find reply "value")
value (some-> value*
[exception value] (try (some->> value*
second
bytes->string
read-fn)
read-fn
(vector nil))
(catch Exception e
[e nil]))
status (get reply "status")
status (set (map (comp keyword bytes->string) status))
error? (contains? status :error)
done? (or error? (contains? status :done))
error? (or exception (contains? status :error))
done? (or error? exception (contains? status :done))
[ex-message ex-data]
(when error?
[(or (some-> (get reply "ex-message")
@ -151,8 +215,9 @@
:vars (bencode->vars pod name-str v)}))
chan (get @chans id)
promise? (instance? clojure.lang.IPending chan)
exception (when (and promise? error?)
(ex-info ex-message ex-data))
exception (or exception
(when (and promise? error?)
(ex-info ex-message ex-data)))
;; NOTE: if we need more fine-grained handlers, we will add
;; a :raw handler that will just get the bencode message's raw
;; data
@ -168,9 +233,12 @@
;; listening to output synchronous.
(when out
(binding [*out* out-stream]
(println out)))
(when err (binding [*out* err-stream]
(println err)))
(print out)
(.flush ^java.io.Writer out-stream)))
(when err
(binding [*out* err-stream]
(print err)
(.flush ^java.io.Writer err-stream)))
(when (or value* error? namespace)
(cond promise?
(deliver chan (cond error? exception
@ -189,7 +257,7 @@
(recur))))
(catch Exception e
(binding [*out* *err* #_err-stream]
(prn e))))))
(prn e)))))))
(def pods (atom {}))
@ -201,27 +269,24 @@
(defn lookup-pod [pod-id]
(get @pods pod-id))
(defn destroy* [{:keys [:stdin :process :ops]}]
(if (contains? ops :shutdown)
(do (write stdin
{"op" "shutdown"
"id" (next-id)})
(.waitFor ^Process process))
(.destroy ^Process process)))
(defn destroy [pod-id-or-pod]
(let [pod-id (get-pod-id pod-id-or-pod)]
(when-let [pod (lookup-pod pod-id)]
(if (contains? (:ops pod) :shutdown)
(do (write (:stdin pod)
{"op" "shutdown"
"id" (next-id)})
(.waitFor ^Process (:process pod)))
(.destroy ^Process (:process pod)))
(destroy* pod)
(when-let [rns (:remove-ns pod)]
(doseq [[ns-name _] (:namespaces pod)]
(rns ns-name))))
(swap! pods dissoc pod-id)
nil))
(def next-pod-id
(let [counter (atom 0)]
(fn []
(let [[o _] (swap-vals! counter inc)]
o))))
(def bytes->symbol
(comp symbol bytes->string))
@ -244,7 +309,8 @@
the socket is connected."
^Socket
[^String hostname ^Integer port]
(Socket. hostname port))
(doto (Socket. hostname port)
(.setTcpNoDelay true)))
(defn close-socket
"Close the socket, and also closes its input and output streams."
@ -254,7 +320,8 @@
(catch java.net.SocketException _ nil)))
(defn port-file [pid]
(io/file (str ".babashka-pod-" pid ".port")))
(doto (io/file (str ".babashka-pod-" pid ".port"))
(.deleteOnExit)))
(defn read-port [^java.io.File port-file]
(loop []
@ -263,37 +330,35 @@
(let [s (slurp f)]
(when (str/ends-with? s "\n")
(str/trim s))))]
(Integer. s)
(Integer/parseInt s)
(recur)))))
(defn debug [& strs]
(binding [*out* *err*]
(println (str/join " " (map pr-str strs)))))
;; TODO: symbol -> look up pod in local cache, invoke if present, else
;; download via package.
;; What about versions?
;; bb can package definitions of popular pods in its resources
;; but what if the resources have an error - maybe best to fetch the definitions from github
;; (load-pod 'org.babashka/postgresql)
;; (load-pod 'org.babashka/postgresql_0.0.1)
(defn load-pod
([pod-spec] (load-pod pod-spec nil))
([pod-spec opts]
(let [{:keys [:version :force]} opts
resolved (when (qualified-symbol? pod-spec)
(defn resolve-pod [pod-spec {:keys [:version :path :force] :as opts}]
(when (qualified-symbol? pod-spec)
(when (and (not version) (not path))
(throw (IllegalArgumentException. "Version or path must be provided")))
(when (and version path)
(throw (IllegalArgumentException. "You must provide either version or path, not both"))))
(let [resolved (when (and (qualified-symbol? pod-spec) version)
(resolver/resolve pod-spec version force))
opts (if resolved
(if-let [extra-opts (:options resolved)]
(merge opts extra-opts)
opts)
opts)
{:keys [:remove-ns :resolve :transport]} opts
pod-spec (cond resolved [(:executable resolved)]
pod-spec (cond
resolved [(:executable resolved)]
path [path]
(string? pod-spec) [pod-spec]
:else pod-spec)
pb (ProcessBuilder. ^java.util.List pod-spec)
:else pod-spec)]
{:pod-spec pod-spec, :opts opts}))
(defn run-pod [pod-spec {:keys [:transport] :as _opts}]
(let [pb (ProcessBuilder. ^java.util.List pod-spec)
socket? (identical? :socket transport)
_ (if socket?
(.inheritIO pb)
@ -316,14 +381,53 @@
[socket
(.getOutputStream socket)
(PushbackInputStream. (.getInputStream socket))])
[nil (.getOutputStream p) (java.io.PushbackInputStream. (.getInputStream p))])
_ (write stdin {"op" "describe"
[nil (.getOutputStream p) (java.io.PushbackInputStream. (.getInputStream p))])]
{:process p
:socket socket
:stdin stdin
:stdout stdout}))
(defn describe-pod [{:keys [:stdin :stdout]}]
(write stdin {"op" "describe"
"id" (next-id)})
reply (read stdout)
format (-> (get reply "format") bytes->string keyword)
ops (some->> (get reply "ops") keys (map keyword) set)
(read stdout))
(defn describe->ops [describe-reply]
(some->> (get describe-reply "ops") keys (map keyword) set))
(defn describe->metadata [describe-reply resolve-fn]
(let [format (-> (get describe-reply "format") bytes->string keyword)
ops (describe->ops describe-reply)
readers (when (identical? :edn format)
(read-readers reply resolve))
(read-readers describe-reply resolve-fn))]
{:format format, :ops ops, :readers readers}))
(defn run-pod-for-metadata [pod-spec opts]
(let [running-pod (run-pod pod-spec opts)
describe-reply (describe-pod running-pod)
ops (describe->ops describe-reply)]
(destroy* (assoc running-pod :ops ops))
describe-reply))
(defn load-pod-metadata [unresolved-pod-spec {:keys [:download-only] :as opts}]
(let [{:keys [:pod-spec :opts]} (resolve-pod unresolved-pod-spec opts)]
(if download-only
(resolver/warn "Not running pod" unresolved-pod-spec "to pre-cache metadata because OS and/or arch are different than system")
(run-pod-for-metadata pod-spec opts))))
(defn load-pod
([pod-spec] (load-pod pod-spec nil))
([pod-spec opts]
(let [{:keys [:pod-spec :opts]} (resolve-pod pod-spec opts)
{:keys [:remove-ns :resolve]} opts
{p :process, stdin :stdin, stdout :stdout, socket :socket
:as running-pod}
(run-pod pod-spec opts)
reply (or (:metadata opts)
(describe-pod running-pod))
{:keys [:format :ops :readers]} (describe->metadata reply resolve)
pod {:process p
:pod-spec pod-spec
:stdin stdin

View file

@ -15,29 +15,43 @@
"x86_64"
arch))
(def os {:os/name (System/getProperty "os.name")
:os/arch (let [arch (System/getProperty "os.arch")]
(normalize-arch arch))})
(defn normalize-os [os]
(-> os str/lower-case (str/replace #"\s+" "_")))
(def os
(delay
{:os/name (or (System/getenv "BABASHKA_PODS_OS_NAME")
(System/getProperty "os.name"))
:os/arch (let [arch (or (System/getenv "BABASHKA_PODS_OS_ARCH")
(System/getProperty "os.arch"))]
(normalize-arch arch))}))
(defn warn [& strs]
(binding [*out* *err*]
(apply println strs)))
(defn match-artifacts [package]
(defn match-artifacts
([package] (match-artifacts package (:os/arch @os)))
([package arch]
(let [artifacts (:pod/artifacts package)
res (filter (fn [{os-name :os/name
os-arch :os/arch}]
(let [os-arch (normalize-arch os-arch)]
(and (re-matches (re-pattern os-name) (:os/name os))
(and (re-matches (re-pattern os-name) (:os/name @os))
(re-matches (re-pattern os-arch)
(:os/arch os)))))
arch))))
artifacts)]
(when (empty? res)
(if (empty? res)
(if (and (= "Mac OS X" (:os/name @os))
(= "aarch64" (:os/arch @os)))
;; Rosetta2 fallback on Apple M1 machines
(match-artifacts package "x86_64")
(throw (IllegalArgumentException. (format "No executable found for pod %s (%s) and OS %s/%s"
(:pod/name package)
(:pod/version package)
(:os/name os)
(:os/arch os)))))
res))
(:os/name @os)
(:os/arch @os)))))
res))))
(defn unzip [{:keys [^java.io.File zip-file
^java.io.File destination-dir
@ -77,7 +91,10 @@
^"[Ljava.nio.file.CopyOption;"
(into-array
[java.nio.file.StandardCopyOption/REPLACE_EXISTING])))
(sh "tar" "xf" (.getPath tmp-file) "--directory" (.getPath destination-dir))
(.mkdirs destination-dir)
(let [res (sh "tar" "xf" (.getPath tmp-file) "--directory" (.getPath destination-dir))]
(when-not (zero? (:exit res))
(throw (ex-info (:err res) res))))
(.delete tmp-file)))
(defn make-executable [dest-dir executables verbose?]
@ -97,11 +114,20 @@
(with-open [is (.getInputStream conn)]
(io/copy is dest))))
(def pod-manifests-dir
;; wrapped in delay for GraalVM native-image
(delay (io/file (or (System/getenv "XDG_DATA_HOME")
(defn repo-dir []
(io/file (if-let [pods-dir (System/getenv "BABASHKA_PODS_DIR")]
(io/file pods-dir)
(io/file (or
(System/getenv "XDG_DATA_HOME")
(System/getProperty "user.home"))
".babashka" "pods" "repository")))
".babashka"
"pods"))
"repository"))
(def pods-repo-dir
;; wrapped in delay for GraalVM native-image
(delay
(repo-dir)))
(defn github-url [qsym version]
(format
@ -110,7 +136,7 @@
(defn pod-manifest
[qsym version force?]
(let [f (io/file @pod-manifests-dir (str qsym) (str version) "manifest.edn")]
(let [f (io/file @pods-repo-dir (str qsym) (str version) "manifest.edn")]
(if (and (not force?)
(.exists f))
(edn/read-string (slurp f))
@ -121,27 +147,30 @@
^java.io.File
[{pod-name :pod/name
pod-version :pod/version}]
(let [base-file
(if-let [pods-dir (System/getenv "BABASHKA_PODS_DIR")]
(io/file pods-dir)
(io/file (or
(System/getenv "XDG_CACHE_HOME")
(System/getProperty "user.home"))
".babashka"
"pods"
"pods"))]
(io/file base-file
"repository"
(str pod-name)
pod-version))
pod-version
(normalize-os (:os/name @os))
(:os/arch @os))))
(defn data-dir
^java.io.File
[{pod-name :pod/name
pod-version :pod/version}]
(io/file (or
(System/getenv "XDG_DATA_HOME")
(System/getProperty "user.home"))
".babashka"
"pods"
"repository"
(io/file @pods-repo-dir
(str pod-name)
pod-version))
pod-version
(normalize-os (:os/name @os))
(:os/arch @os)))
(defn sha256 [file]
(let [buf (byte-array 8192)

View file

@ -12,11 +12,12 @@
(defn- process-namespace [{:keys [:name :vars]}]
(binding [*ns* (load-string (format "(ns %s) *ns*" name))]
(doseq [[var-sym v] vars]
(when-let [maybe-core (some-> (ns-resolve *ns* var-sym) meta :ns str symbol)]
(when (= 'clojure.core maybe-core)
(ns-unmap *ns* var-sym)))
(cond
(ifn? v)
(do
(ns-unmap *ns* var-sym)
(intern name var-sym v))
(intern name var-sym v)
(string? v)
(load-string v)))))
@ -55,9 +56,10 @@
(when defer?
[ns-name pod]))
namespaces)))
(binding [impl/*pod-id* (:pod-id pod)]
(doseq [[ns-sym vars lazy?] namespaces
:when (not lazy?)]
(process-namespace {:name ns-sym :vars vars}))
(process-namespace {:name ns-sym :vars vars})))
(future (impl/processor pod))
{:pod/id (:pod-id pod)})))
@ -69,3 +71,18 @@
(defn invoke
([pod-id sym args] (invoke pod-id sym args {}))
([pod-id sym args opts] (impl/invoke-public pod-id sym args opts)))
(defmacro copy-var [name var]
`(do (def ~name ~var)
(let [m# (meta (var ~var))
doc# (:doc m#)
arglists# (:arglists m#)]
(alter-meta! (var ~name) assoc
:arglists arglists#
:doc doc#))))
#_:clj-kondo/ignore
(do
(copy-var add-transit-read-handler! impl/add-transit-read-handler!)
(copy-var add-transit-write-handler! impl/add-transit-write-handler!)
(copy-var set-default-transit-write-handler! impl/set-default-transit-write-handler!))

View file

@ -1,6 +1,12 @@
(ns babashka.pods.sci
(:require [babashka.pods.impl :as impl]
[sci.core :as sci]))
[sci.core :as sci]
[clojure.java.io :as io]
[babashka.pods.impl.resolver :as resolver]
[babashka.fs :as fs])
(:import (java.io PushbackInputStream File)))
(set! *warn-on-reflection* true)
(defn- process-namespace [ctx {:keys [:name :vars]}]
(let [env (:env ctx)
@ -11,14 +17,58 @@
(swap! env update-in [:namespaces ns-name]
(fn [ns-map]
(if ns-map ns-map {:obj sci-ns})))
(doseq [[var-name var-value] vars]
(doseq [[var-name var-value :as var] vars]
(cond (ifn? var-value)
(swap! env assoc-in [:namespaces ns-name var-name]
(sci/new-var
(symbol (str ns-name) (str var-name)) var-value))
(symbol (str ns-name) (str var-name)) var-value (meta var-name)))
(string? var-value)
(sci/eval-string* ctx var-value))))))
(defn metadata-cache-file ^File [^File bb-edn-file pod-spec {:keys [:version :path]}]
(if version
(io/file (resolver/cache-dir {:pod/name pod-spec :pod/version version})
"metadata.cache")
(let [config-dir (.getParentFile bb-edn-file)
cache-dir (io/file config-dir ".babashka")
pod-file (-> path io/file .getName)
cache-file (io/file cache-dir (str pod-file ".metadata.cache"))]
cache-file)))
(defn load-metadata-from-cache [bb-edn-file pod-spec opts]
(let [cache-file (metadata-cache-file bb-edn-file pod-spec opts)]
(when (.exists cache-file)
(with-open [r (PushbackInputStream. (io/input-stream cache-file))]
(impl/read r)))))
(defn load-pod-metadata* [bb-edn-file pod-spec {:keys [:version :cache] :as opts}]
(let [metadata (impl/load-pod-metadata pod-spec opts)
cache-file (when (and metadata cache)
(metadata-cache-file bb-edn-file pod-spec opts))]
(when cache-file
(io/make-parents cache-file)
(when (fs/writable? (fs/parent cache-file))
(with-open [w (io/output-stream cache-file)]
(impl/write w metadata))))
metadata))
(defn load-pod-metadata
([pod-spec opts] (load-pod-metadata nil pod-spec opts))
([bb-edn-file pod-spec {:keys [:cache] :as opts}]
(let [metadata
(if-let [cached-metadata (when cache
(load-metadata-from-cache bb-edn-file
pod-spec
opts))]
cached-metadata
(load-pod-metadata* bb-edn-file pod-spec opts))]
(reduce
(fn [pod-namespaces ns]
(let [ns-sym (-> ns (get "name") impl/bytes->string symbol)]
(assoc pod-namespaces ns-sym {:pod-spec pod-spec
:opts (assoc opts :metadata metadata)})))
{} (get metadata "namespaces")))))
(defn load-pod
([ctx pod-spec] (load-pod ctx pod-spec nil))
([ctx pod-spec version opts] (load-pod ctx pod-spec (assoc opts :version version)))
@ -65,9 +115,10 @@
(when prev-load-fn
(prev-load-fn m))))]
(swap! env assoc :load-fn new-load-fn)))
(binding [impl/*pod-id* (:pod-id pod)]
(doseq [[ns-name vars lazy?] namespaces
:when (not lazy?)]
(process-namespace ctx {:name ns-name :vars vars}))
(process-namespace ctx {:name ns-name :vars vars})))
(sci/future (impl/processor pod))
{:pod/id (:pod-id pod)})))
@ -79,3 +130,18 @@
(defn invoke
([pod-id sym args] (invoke pod-id sym args {}))
([pod-id sym args opts] (impl/invoke-public pod-id sym args opts)))
(defmacro copy-var [name var]
`(do (def ~name ~var)
(let [m# (meta (var ~var))
doc# (:doc m#)
arglists# (:arglists m#)]
(alter-meta! (var ~name) assoc
:arglists arglists#
:doc doc#))))
#_:clj-kondo/ignore
(do
(copy-var add-transit-read-handler! impl/add-transit-read-handler!)
(copy-var add-transit-write-handler! impl/add-transit-write-handler!)
(copy-var set-default-transit-write-handler! impl/set-default-transit-write-handler!))

View file

@ -36,12 +36,34 @@
(defn transit-json-read [^String s]
(with-open [bais (java.io.ByteArrayInputStream. (.getBytes s "UTF-8"))]
(let [r (transit/reader bais :json)]
(let [r (transit/reader bais :json {:handlers
{"local-date-time"
(transit/read-handler
(fn [s]
(java.time.LocalDateTime/parse s)))
"java.array"
(transit/read-handler
(fn [v]
(into-array v)))}})]
(transit/read r))))
(defn transit-json-write [^String s]
(defn transit-json-write [s]
(with-open [baos (java.io.ByteArrayOutputStream. 4096)]
(let [w (transit/writer baos :json)]
(let [w (transit/writer baos :json {:handlers
{java.time.LocalDateTime
(transit/write-handler
"local-date-time"
str)}
:default-handler
(transit/write-handler
(fn [x] (when (.isArray (class x)) "java.array"))
vec)})]
(transit/write w s)
(str baos))))
(defn transit-json-write-meta [s]
(with-open [baos (java.io.ByteArrayOutputStream. 4096)]
(let [w (transit/writer baos :json {:transform transit/write-meta})]
(transit/write w s)
(str baos))))
@ -94,7 +116,8 @@
"my/other-tag" "pod.test-pod/read-other-tag"}
"namespaces"
[{"name" "pod.test-pod"
"vars" (into [{"name" "add-sync"}
"vars" (into [{"name" "add-sync"
"meta" "{:doc \"add the arguments\"}"}
{"name" "range-stream"
"async" "true"}
{"name" "assoc"}
@ -111,7 +134,35 @@
{"name" "other-tag"}
;; reads thing with other tag
{"name" "read-other-tag"
"code" "(defn read-other-tag [x] [x x])"}]
"code" "(defn read-other-tag [x] [x x])"
"meta" "{:doc \"unread\"}"}
{"name" "round-trip-meta"
"arg-meta" "true"}
{"name" "dont-round-trip-meta"
"arg-meta" "false"}
{"name" "-local-date-time"}
{"name" "transit-stuff"
"code" "
(babashka.pods/add-transit-read-handler! \"local-date-time\"
(fn [s] (java.time.LocalDateTime/parse s)))
(babashka.pods/add-transit-write-handler! #{java.time.LocalDateTime}
\"local-date-time\"
str )
(defn local-date-time [x]
(-local-date-time x))
;; serialize Java arrays as vectors with tag java.array
(babashka.pods/set-default-transit-write-handler!
(fn [x] (when (.isArray (class x)) \"java.array\"))
vec)
(babashka.pods/add-transit-read-handler! \"java.array\"
into-array)
"}
{"name" "incorrect-edn"}]
dependents)}
{"name" "pod.test-pod.loaded"
"defer" "true"}
@ -167,14 +218,14 @@
"id" id})
pod.test-pod/print
(do (write out
{"out" (pr-str args)
{"out" (with-out-str (prn args))
"id" id})
(write out
{"status" ["done"]
"id" id}))
pod.test-pod/print-err
(do (write out
{"err" (pr-str args)
{"err" (with-out-str (prn args))
"id" id})
(write out
{"status" ["done"]
@ -193,7 +244,33 @@
(write out
{"status" ["done"]
"id" id
"value" "#my/other-tag[1]"}))
"value" "#my/other-tag[1]"})
pod.test-pod/round-trip-meta
(write out
{"status" ["done"]
"id" id
"value"
(case format
:transit+json (transit-json-write-meta (first args))
(write-fn (first args)))})
pod.test-pod/dont-round-trip-meta
(write out
{"status" ["done"]
"id" id
"value"
(case format
:transit+json (transit-json-write-meta (first args))
(write-fn (first args)))})
pod.test-pod/-local-date-time
(write out
{"status" ["done"]
"id" id
"value" (write-fn (first args))})
pod.test-pod/incorrect-edn
(write out
{"status" ["done"]
"id" id
"value" (write-fn {(keyword "foo bar") 1})}))
(recur))
:shutdown (System/exit 0)
:load-ns (let [ns (-> (get message "ns")

View file

@ -1,6 +1,6 @@
(require '[babashka.pods :as pods])
(pods/load-pod 'org.babashka/buddy "0.0.1")
(pods/load-pod 'org.babashka/buddy "0.1.0")
(require '[pod.babashka.buddy.codecs :as codecs]
'[pod.babashka.buddy.hash :as hash])
@ -8,4 +8,4 @@
(println (-> (hash/sha256 "foobar")
(codecs/bytes->hex)))
(pods/load-pod 'org.babashka/etaoin) ;; should cause error when version is missing
(pods/load-pod 'org.babashka/etaoin) ;; should cause error when version & path are missing

View file

@ -5,9 +5,13 @@
(def socket (System/getenv "BABASHKA_POD_TEST_SOCKET"))
(def pod-id (:pod/id (pods/load-pod (cond-> ["clojure" "-A:test-pod"]
(def cmd (cond-> ["clojure" "-M:test-pod"]
(= "json" fmt) (conj "--json")
(= "transit+json" fmt) (conj "--transit+json"))
(= "transit+json" fmt) (conj "--transit+json")))
;; (.println System/err cmd)
(def pod-id (:pod/id (pods/load-pod cmd
{:socket (boolean socket)})))
(require '[pod.test-pod :as pod])
@ -51,6 +55,10 @@
(def add-result (pod.test-pod/add-sync 1 2 3))
(def nil-result (pod.test-pod/return-nil))
(def add-sync-meta (:doc (meta #'pod.test-pod/add-sync)))
(def error-meta (:doc (meta #'pod.test-pod/error)))
(def read-other-tag-meta (:doc (meta #'pod.test-pod/read-other-tag)))
(def x9 pod.test-pod/x9)
(def tagged (if (= "edn" fmt)
@ -64,12 +72,43 @@
(def fn-called (pod.test-pod/fn-call inc 2))
;; (.println System/err (str :fmt " " fmt))
(def local-date-time
(if (= "transit+json" fmt)
(instance? java.time.LocalDateTime (pod.test-pod/local-date-time (java.time.LocalDateTime/now)))
true))
(def assoc-string-array
(if (= "transit+json" fmt)
(let [v (:a (pod.test-pod/assoc {} :a (into-array String ["foo"])))]
(.isArray (class v)))
true))
(def round-trip-meta
(if (= "transit+json" fmt)
(= {:my-meta 2} (meta (pod.test-pod/round-trip-meta (with-meta [2] {:my-meta 2}))))
true))
(def round-trip-meta-nested
(if (= "transit+json" fmt)
(= {:my-meta 3} (meta (first (pod.test-pod/round-trip-meta [(with-meta [3] {:my-meta 3})]))))
true))
(def dont-round-trip-meta
(if (= "transit+json" fmt)
(= nil (meta (pod.test-pod/dont-round-trip-meta (with-meta [2] {:my-meta 2}))))
true))
(require '[pod.test-pod.only-code :as only-code])
(def should-be-1 (only-code/foo))
(require '[pod.test-pod.loaded2 :as loaded2])
(def loaded (loaded2/loaded 1))
(def incorrect-edn-response
(try (pod.test-pod/incorrect-edn)
(catch Exception e (ex-message e))))
(pods/unload-pod pod-id)
(def successfully-removed (nil? (find-ns 'pod.test-pod)))
@ -90,4 +129,13 @@
other-tagged
loaded
fn-called
should-be-1]
local-date-time
assoc-string-array
round-trip-meta
round-trip-meta-nested
dont-round-trip-meta
should-be-1
add-sync-meta
error-meta
read-other-tag-meta
incorrect-edn-response]

View file

@ -0,0 +1,11 @@
(ns babashka.pods.impl-test
(:require [clojure.test :refer :all]
[babashka.pods.impl :refer :all]))
(deftest load-pod-test
(testing "resolve fn gets called when pod has EDN data readers"
(let [resolved? (atom false)
test-resolve (fn [_sym]
(reset! resolved? true))]
(load-pod ["clojure" "-M:test-pod"] {:resolve test-resolve})
(is @resolved?))))

View file

@ -25,4 +25,4 @@
(catch Exception e
e)))]
(is (str/includes? (str out) "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"))
(is (str/includes? (pr-str ex) "Version must be provided" ))))
(is (str/includes? (pr-str ex) "Version or path must be provided"))))

View file

@ -13,12 +13,19 @@
{'load-pod (fn [& args]
(apply pods/load-pod @ctx-ref args))
'invoke pods/invoke
'unload-pod pods/unload-pod}}
:classes {'System System}})
'unload-pod pods/unload-pod
'add-transit-read-handler! pods/add-transit-read-handler!
'add-transit-write-handler! pods/add-transit-write-handler!
'set-default-transit-write-handler! pods/set-default-transit-write-handler!}}
:classes {'System System
'java.time.LocalDateTime java.time.LocalDateTime
'java.lang.Class Class}})
_ (vreset! ctx-ref ctx)
ret (sci/binding [sci/out out
sci/err err]
(sci/eval-string* ctx test-program))]
(binding [*out* out
*err* err]
(sci/eval-string* ctx test-program)))]
(assertions out err ret)))
(deftest pod-registry-test
@ -31,4 +38,4 @@
(catch Exception e
e)))]
(is (str/includes? (str out) "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"))
(is (str/includes? (pr-str ex) "Version must be provided" ))))
(is (str/includes? (pr-str ex) "Version or path must be provided"))))

View file

@ -1,12 +1,21 @@
(ns babashka.pods.test-common
(:require [clojure.java.io :as io]
[clojure.string :as str]
[clojure.test :refer [is]]))
(def test-program (slurp (io/file "test-resources" "test_program.clj")))
(defn assertions [out err ret]
;; (.println System/err ret)
;; (.println System/err out)
;; (.println System/err err)
(doseq [[expected actual]
(map vector '["pod.test-pod"
(map vector (replace
{::edn-error (if (= "edn"
(System/getenv "BABASHKA_POD_TEST_FORMAT"))
"Map literal must contain an even number of forms"
::dont-care)}
'["pod.test-pod"
pod.test-pod
{:a 1, :b 2}
6
@ -23,12 +32,23 @@
[[1] [1]]
2
3
1]
true ;; local-date
true ;; roundtrip string array
true ;; roundtrip metadata
true ;; roundtrip metadata nested
true ;; dont roundtrip metadata (when arg-meta "false"/ absent)
1
"add the arguments"
nil
nil
::edn-error])
(concat ret (repeat ::nil)))]
(if (instance? java.util.regex.Pattern expected)
(cond (instance? java.util.regex.Pattern expected)
(is (re-find expected actual))
(= ::dont-care expected) nil
:else
(is (= expected actual))))
(is (= "(\"hello\" \"print\" \"this\" \"debugging\" \"message\")\n:foo\n:foo\n" (str out)))
(is (= "(\"hello\" \"print\" \"this\" \"error\")\n" (str err))))
(is (str/starts-with? (str err) "(\"hello\" \"print\" \"this\" \"error\")" )))
(def pod-registry (slurp (io/file "test-resources" "pod_registry.clj")))