[#411] pods
This commit is contained in:
parent
d5f9a1914e
commit
eb4f25f5c5
26 changed files with 1398 additions and 9 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -20,3 +20,5 @@ pom.xml.asc
|
||||||
/tmp
|
/tmp
|
||||||
/reports
|
/reports
|
||||||
*.dylib
|
*.dylib
|
||||||
|
*.log
|
||||||
|
org_babashka*.h
|
||||||
|
|
|
||||||
3
doc/pods.md
Normal file
3
doc/pods.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Pods
|
||||||
|
|
||||||
|
Pods are commmand line programs that babashka can communicate with via stdin and stdout.
|
||||||
1
examples/pods/pod-babashka-filewatcher/.gitignore
vendored
Normal file
1
examples/pods/pod-babashka-filewatcher/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
292
examples/pods/pod-babashka-filewatcher/Cargo.lock
generated
Normal file
292
examples/pods/pod-babashka-filewatcher/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f59efc38004c988e4201d11d263b8171f49a2e7ec0bdbb71773433f271504a5e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"winapi 0.3.8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fsevent-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent-sys"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fuchsia-zircon"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fuchsia-zircon-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fuchsia-zircon-sys"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24e40d6fd5d64e2082e0c796495c8ef5ad667a96d03e5aaa0becfd9d47bcbfb8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"inotify-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify-sys"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iovec"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "json"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kernel32-sys"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.2.8",
|
||||||
|
"winapi-build",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazycell"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.6.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"fuchsia-zircon",
|
||||||
|
"fuchsia-zircon-sys",
|
||||||
|
"iovec",
|
||||||
|
"kernel32-sys",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"miow",
|
||||||
|
"net2",
|
||||||
|
"slab",
|
||||||
|
"winapi 0.2.8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio-extras"
|
||||||
|
version = "2.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
|
||||||
|
dependencies = [
|
||||||
|
"lazycell",
|
||||||
|
"log",
|
||||||
|
"mio",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miow"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
|
||||||
|
dependencies = [
|
||||||
|
"kernel32-sys",
|
||||||
|
"net2",
|
||||||
|
"winapi 0.2.8",
|
||||||
|
"ws2_32-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "net2"
|
||||||
|
version = "0.2.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"winapi 0.3.8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify"
|
||||||
|
version = "4.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"filetime",
|
||||||
|
"fsevent",
|
||||||
|
"fsevent-sys",
|
||||||
|
"inotify",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"mio-extras",
|
||||||
|
"walkdir",
|
||||||
|
"winapi 0.3.8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pod-babashka-filewatcher"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"json",
|
||||||
|
"log",
|
||||||
|
"notify",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.1.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi 0.3.8",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-build"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.3.8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ws2_32-sys"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||||
|
dependencies = [
|
||||||
|
"winapi 0.2.8",
|
||||||
|
"winapi-build",
|
||||||
|
]
|
||||||
12
examples/pods/pod-babashka-filewatcher/Cargo.toml
Normal file
12
examples/pods/pod-babashka-filewatcher/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "pod-babashka-filewatcher"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Michiel Borkent <michielborkent@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
notify = "4.0.12"
|
||||||
|
json = "0.12.4"
|
||||||
|
log = "0.4"
|
||||||
18
examples/pods/pod-babashka-filewatcher/README.md
Normal file
18
examples/pods/pod-babashka-filewatcher/README.md
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# pod-babashka-filewatcher
|
||||||
|
|
||||||
|
## Compile
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```
|
||||||
|
(babashka.pods/load-pod "examples/pods/pod-babashka-filewatcher/target/release/pod-babashka-filewatcher")
|
||||||
|
(def chan (pod.babashka.filewatcher/watch "/tmp"))
|
||||||
|
(require '[clojure.core.async :as async])
|
||||||
|
(loop [] (prn (async/<!! chan)) (recur))
|
||||||
|
;;=> ["changed" "/tmp"]
|
||||||
|
;;=> ["changed" "/tmp"]
|
||||||
|
```
|
||||||
320
examples/pods/pod-babashka-filewatcher/src/bencode.rs
Normal file
320
examples/pods/pod-babashka-filewatcher/src/bencode.rs
Normal file
|
|
@ -0,0 +1,320 @@
|
||||||
|
// from https://github.com/jasilven/redbush/blob/master/src/nrepl/bencode.rs
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::hash::Hasher;
|
||||||
|
use std::io::BufRead;
|
||||||
|
use std::iter::Iterator;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::string::ToString;
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, BencodeError>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum BencodeError {
|
||||||
|
Error(String),
|
||||||
|
Io(std::io::Error),
|
||||||
|
Eof(),
|
||||||
|
Parse(std::num::ParseIntError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BencodeError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
BencodeError::Error(s) => write!(f, "Bencode Error: {} ", s),
|
||||||
|
BencodeError::Io(e) => write!(f, "Bencode Io: {}", e),
|
||||||
|
BencodeError::Parse(e) => write!(f, "Bencode Parse: {}", e),
|
||||||
|
BencodeError::Eof() => write!(f, "Bencode Eof"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for BencodeError {
|
||||||
|
fn from(err: std::io::Error) -> BencodeError {
|
||||||
|
BencodeError::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::num::ParseIntError> for BencodeError {
|
||||||
|
fn from(err: std::num::ParseIntError) -> BencodeError {
|
||||||
|
BencodeError::Parse(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq)]
|
||||||
|
pub struct HMap(pub HashMap<Value, Value>);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Value {
|
||||||
|
Map(HMap),
|
||||||
|
List(Vec<Value>),
|
||||||
|
Str(String),
|
||||||
|
Int(i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Value {
|
||||||
|
fn from(s: &str) -> Self {
|
||||||
|
Value::Str(s.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HashMap<Value, Value>> for Value {
|
||||||
|
fn from(m: HashMap<Value, Value>) -> Self {
|
||||||
|
Value::Map(HMap::new(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HashMap<&str, &str>> for Value {
|
||||||
|
fn from(map: HashMap<&str, &str>) -> Self {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
for (k, v) in map {
|
||||||
|
m.insert(Value::Str(k.to_string()), Value::Str(v.to_string()));
|
||||||
|
}
|
||||||
|
let hm = HMap::new(m);
|
||||||
|
Value::Map(hm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<HashMap<String, String>> for Value {
|
||||||
|
type Error = BencodeError;
|
||||||
|
|
||||||
|
fn try_into(self) -> std::result::Result<HashMap<String, String>, Self::Error> {
|
||||||
|
match self {
|
||||||
|
Value::Map(hm) => {
|
||||||
|
let mut map = HashMap::<String, String>::new();
|
||||||
|
for key in hm.0.keys() {
|
||||||
|
// safe to unwrap here
|
||||||
|
map.insert(format!("{}", &key), format!("{}", &hm.get(key).unwrap()));
|
||||||
|
}
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
_ => Err(BencodeError::Error("Expected HashMap Value".into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HMap {
|
||||||
|
pub fn new(map: HashMap<Value, Value>) -> Self {
|
||||||
|
HMap(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, key: &Value) -> Option<&Value> {
|
||||||
|
self.0.get(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for HMap {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
let mut keys: Vec<String> = self.0.keys().map(|k| format!("{:?}", k)).collect();
|
||||||
|
let mut vals: Vec<String> = self.0.values().map(|v| format!("{:?}", v)).collect();
|
||||||
|
keys.sort();
|
||||||
|
vals.sort();
|
||||||
|
keys.hash(state);
|
||||||
|
vals.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for HMap {
|
||||||
|
fn eq(&self, other: &HMap) -> bool {
|
||||||
|
self.0.eq(&other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Value {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Value::Map(hm) => {
|
||||||
|
let mut result = String::from("{");
|
||||||
|
for (key, val) in hm.0.iter() {
|
||||||
|
result.push_str(&format!("{} {} ", &key, &val));
|
||||||
|
}
|
||||||
|
let mut result = result.trim_end().to_string();
|
||||||
|
result.push('}');
|
||||||
|
write!(f, "{}", result)
|
||||||
|
}
|
||||||
|
Value::List(v) => {
|
||||||
|
let mut result = String::from("[");
|
||||||
|
for item in v {
|
||||||
|
result.push_str(&item.to_string());
|
||||||
|
result.push_str(", ");
|
||||||
|
}
|
||||||
|
let mut result = result
|
||||||
|
.trim_end_matches(|c| c == ',' || c == ' ')
|
||||||
|
.to_string();
|
||||||
|
result.push(']');
|
||||||
|
write!(f, "{}", result)
|
||||||
|
}
|
||||||
|
Value::Str(s) => write!(f, "{}", s),
|
||||||
|
Value::Int(i) => write!(f, "{}", i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub fn to_bencode(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Value::Map(hm) => {
|
||||||
|
let mut result = String::from("d");
|
||||||
|
for (key, val) in hm.0.iter() {
|
||||||
|
result.push_str(&format!("{}{}", key.to_bencode(), val.to_bencode()));
|
||||||
|
}
|
||||||
|
result.push('e');
|
||||||
|
result
|
||||||
|
}
|
||||||
|
Value::List(v) => {
|
||||||
|
let mut result = String::from("l");
|
||||||
|
for item in v {
|
||||||
|
result.push_str(&item.to_bencode());
|
||||||
|
}
|
||||||
|
result.push('e');
|
||||||
|
result
|
||||||
|
}
|
||||||
|
Value::Str(s) => format!("{}:{}", s.len(), s),
|
||||||
|
Value::Int(i) => format!("i{}e", i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_bencode(reader: &mut dyn BufRead) -> Result<Option<Value>> {
|
||||||
|
log::debug!("Parsing bencode from reader");
|
||||||
|
|
||||||
|
let mut buf = vec![];
|
||||||
|
buf.resize(1, 0);
|
||||||
|
match reader.read_exact(&mut buf[0..1]) {
|
||||||
|
Ok(()) => match buf[0] {
|
||||||
|
b'i' => match reader.read_until(b'e', &mut buf) {
|
||||||
|
Ok(cnt) => {
|
||||||
|
let s = String::from_utf8_lossy(&buf[1..cnt]);
|
||||||
|
let n = i32::from_str(&s)?;
|
||||||
|
Ok(Some(Value::Int(n)))
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
},
|
||||||
|
b'd' => {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
loop {
|
||||||
|
match parse_bencode(reader) {
|
||||||
|
Ok(None) => return Ok(Some(Value::Map(HMap(map)))),
|
||||||
|
Ok(Some(v)) => map.insert(v, parse_bencode(reader)?.unwrap()),
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b'l' => {
|
||||||
|
let mut list = Vec::<Value>::new();
|
||||||
|
loop {
|
||||||
|
match parse_bencode(reader) {
|
||||||
|
Ok(None) => return Ok(Some(Value::List(list))),
|
||||||
|
Ok(Some(v)) => list.push(v),
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b'e' => Ok(None),
|
||||||
|
b'0' => {
|
||||||
|
reader.read_until(b':', &mut buf)?;
|
||||||
|
Ok(Some(Value::Str("".to_string())))
|
||||||
|
}
|
||||||
|
_ => match reader.read_until(b':', &mut buf) {
|
||||||
|
Ok(_) => {
|
||||||
|
buf.resize(buf.len() - 1, 0);
|
||||||
|
let mut s = String::from("");
|
||||||
|
buf.iter().for_each(|i| s.push(*i as char));
|
||||||
|
let cnt = usize::from_str(&s)?;
|
||||||
|
buf.resize(cnt, 0);
|
||||||
|
reader.read_exact(&mut buf[0..cnt])?;
|
||||||
|
Ok(Some(Value::Str(
|
||||||
|
String::from_utf8_lossy(&buf[..]).to_string(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
Err(e) => Err(BencodeError::Io(e)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
std::io::ErrorKind::UnexpectedEof => (Err(BencodeError::Eof())),
|
||||||
|
_ => Err(BencodeError::Io(e)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_bencode_num() {
|
||||||
|
let left = vec![
|
||||||
|
Value::Int(1),
|
||||||
|
Value::Int(10),
|
||||||
|
Value::Int(100_000),
|
||||||
|
Value::Int(-1),
|
||||||
|
Value::Int(-999),
|
||||||
|
];
|
||||||
|
let right = vec!["i1e", "i10e", "i100000e", "i-1e", "i-999e"];
|
||||||
|
|
||||||
|
for i in 0..left.len() {
|
||||||
|
let mut bufread = BufReader::new(right[i].as_bytes());
|
||||||
|
assert_eq!(left[i], parse_bencode(&mut bufread).unwrap().unwrap());
|
||||||
|
assert_eq!(left[i].to_bencode(), right[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_bencode_str() {
|
||||||
|
let left = vec![
|
||||||
|
Value::Str("foo".to_string()),
|
||||||
|
Value::Str("1234567890\n".to_string()),
|
||||||
|
Value::Str("".to_string()),
|
||||||
|
];
|
||||||
|
let right = vec!["3:foo", "11:1234567890\n", "0:"];
|
||||||
|
for i in 0..left.len() {
|
||||||
|
let mut bufread = BufReader::new(right[i].as_bytes());
|
||||||
|
assert_eq!(left[i], parse_bencode(&mut bufread).unwrap().unwrap());
|
||||||
|
assert_eq!(left[i].to_bencode(), right[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_bencode_list() {
|
||||||
|
let left = vec![
|
||||||
|
(Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)])),
|
||||||
|
(Value::List(vec![
|
||||||
|
Value::Int(1),
|
||||||
|
Value::Str("foo".to_string()),
|
||||||
|
Value::Int(3),
|
||||||
|
])),
|
||||||
|
(Value::List(vec![Value::Str("".to_string())])),
|
||||||
|
];
|
||||||
|
let right = vec!["li1ei2ei3ee", "li1e3:fooi3ee", "l0:e"];
|
||||||
|
for i in 0..left.len() {
|
||||||
|
let mut bufread = BufReader::new(right[i].as_bytes());
|
||||||
|
assert_eq!(left[i], parse_bencode(&mut bufread).unwrap().unwrap());
|
||||||
|
assert_eq!(left[i].to_bencode(), right[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_bencode_map() {
|
||||||
|
let mut m1 = HashMap::new();
|
||||||
|
m1.insert(Value::Str("bar".to_string()), Value::Str("baz".to_string()));
|
||||||
|
let m1_c = m1.clone();
|
||||||
|
let left1 = Value::Map(HMap::new(m1));
|
||||||
|
|
||||||
|
let mut m2 = HashMap::new();
|
||||||
|
m2.insert(Value::Str("foo".to_string()), Value::Map(HMap::new(m1_c)));
|
||||||
|
let left2 = Value::Map(HMap::new(m2));
|
||||||
|
|
||||||
|
let sright1 = "d3:bar3:baze".to_string();
|
||||||
|
let mut right1 = BufReader::new(sright1.as_bytes());
|
||||||
|
assert_eq!(left1, parse_bencode(&mut right1).unwrap().unwrap());
|
||||||
|
assert_eq!(left1.to_bencode(), sright1);
|
||||||
|
|
||||||
|
let sright2 = "d3:food3:bar3:bazee".to_string();
|
||||||
|
let mut right2 = BufReader::new(sright2.as_bytes());
|
||||||
|
assert_eq!(left2, parse_bencode(&mut right2).unwrap().unwrap());
|
||||||
|
assert_eq!(left2.to_bencode(), sright2);
|
||||||
|
}
|
||||||
|
}
|
||||||
137
examples/pods/pod-babashka-filewatcher/src/main.rs
Normal file
137
examples/pods/pod-babashka-filewatcher/src/main.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
// see https://github.com/jasilven/redbush/blob/master/src/nrepl/mod.rs
|
||||||
|
|
||||||
|
mod bencode;
|
||||||
|
use bencode::{Value};
|
||||||
|
use bencode as bc;
|
||||||
|
|
||||||
|
use notify::{Watcher, RecursiveMode, watcher};
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{Write, BufReader};
|
||||||
|
|
||||||
|
use json;
|
||||||
|
|
||||||
|
fn get_string(val: &bc::Value, key: &str) -> Option<String> {
|
||||||
|
match val {
|
||||||
|
bc::Value::Map(hm) => {
|
||||||
|
match hm.get(&Value::from(key)) {
|
||||||
|
Some(Value::Str(s)) =>
|
||||||
|
Some(String::from(s)),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(mut m: HashMap<Value,Value>, k: &str, v: &str) -> HashMap<Value,Value> {
|
||||||
|
m.insert(Value::from(k), Value::from(v));
|
||||||
|
m
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe() {
|
||||||
|
let namespace = HashMap::new();
|
||||||
|
let mut namespace = insert(namespace, "name", "pod.babashka.filewatcher");
|
||||||
|
let mut vars = Vec::new();
|
||||||
|
let var_map = HashMap::new();
|
||||||
|
let var_map = insert(var_map, "name", "watch");
|
||||||
|
let var_map = insert(var_map, "async", "true");
|
||||||
|
vars.push(Value::from(var_map));
|
||||||
|
namespace.insert(Value::from("vars"),Value::List(vars));
|
||||||
|
let describe_map = HashMap::new();
|
||||||
|
let mut describe_map = insert(describe_map, "format", "json");
|
||||||
|
let namespaces = vec![Value::from(namespace)];
|
||||||
|
let namespaces = Value::List(namespaces);
|
||||||
|
describe_map.insert(Value::from("namespaces"), namespaces);
|
||||||
|
let describe_map = Value::from(describe_map);
|
||||||
|
let bencode = describe_map.to_bencode();
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut handle = stdout.lock();
|
||||||
|
handle.write_all(bencode.as_bytes()).unwrap();
|
||||||
|
handle.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_changed(id: &str, path: &str) {
|
||||||
|
let reply = HashMap::new();
|
||||||
|
let reply = insert(reply, "id", id);
|
||||||
|
let value = vec!["changed", path];
|
||||||
|
let value = json::stringify(value);
|
||||||
|
let mut reply = insert(reply, "value", &value);
|
||||||
|
let status = vec![Value::from("status")];
|
||||||
|
reply.insert(Value::from("status"),Value::List(status));
|
||||||
|
let bencode = Value::from(reply).to_bencode();
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut handle = stdout.lock();
|
||||||
|
handle.write_all(bencode.as_bytes()).unwrap();
|
||||||
|
handle.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn watch(id: &str, path: &str) {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let mut watcher = watcher(tx, Duration::from_secs(2)).unwrap();
|
||||||
|
watcher.watch(path, RecursiveMode::Recursive).unwrap();
|
||||||
|
loop {
|
||||||
|
match rx.recv() {
|
||||||
|
Ok(_) => {
|
||||||
|
path_changed(id, path);
|
||||||
|
},
|
||||||
|
Err(e) => panic!("watch error: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_incoming(val: bc::Value) {
|
||||||
|
let op = get_string(&val, "op").unwrap();
|
||||||
|
match &op[..] {
|
||||||
|
"describe" => {
|
||||||
|
describe()
|
||||||
|
|
||||||
|
},
|
||||||
|
"invoke" => {
|
||||||
|
let var = get_string(&val, "var").unwrap();
|
||||||
|
match &var[..] {
|
||||||
|
"pod.babashka.filewatcher/watch" => {
|
||||||
|
let args = get_string(&val, "args").unwrap();
|
||||||
|
let args = json::parse(&args).unwrap();
|
||||||
|
let path = &args[0];
|
||||||
|
let path = path.as_str().unwrap();
|
||||||
|
let id = get_string(&val, "id").unwrap();
|
||||||
|
watch(&id, path);
|
||||||
|
},
|
||||||
|
_ => panic!(var)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
_ => panic!(op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut reader = BufReader::new(io::stdin());
|
||||||
|
let val = bc::parse_bencode(&mut reader);
|
||||||
|
match val {
|
||||||
|
Ok(res) => {
|
||||||
|
match res {
|
||||||
|
Some(val) => {
|
||||||
|
handle_incoming(val)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Err(bc::BencodeError::Eof()) => {
|
||||||
|
return
|
||||||
|
},
|
||||||
|
Err(v) => panic!("{}", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
20
examples/pods/pod-babashka-hsqldb/.gitignore
vendored
Normal file
20
examples/pods/pod-babashka-hsqldb/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/target
|
||||||
|
/classes
|
||||||
|
/checkouts
|
||||||
|
profiles.clj
|
||||||
|
pom.xml
|
||||||
|
pom.xml.asc
|
||||||
|
*.jar
|
||||||
|
*.class
|
||||||
|
/.lein-*
|
||||||
|
/.nrepl-port
|
||||||
|
.hgignore
|
||||||
|
.hg/
|
||||||
|
/bb
|
||||||
|
.clj-kondo/.cache
|
||||||
|
!java/src/babashka/impl/LockFix.class
|
||||||
|
!test-resources/babashka/src_for_classpath_test/foo.jar
|
||||||
|
!test-resources/pom.xml
|
||||||
|
.cpcache
|
||||||
|
!reflection.json
|
||||||
|
hsqldb-babashka-plugin
|
||||||
12
examples/pods/pod-babashka-hsqldb/README.md
Normal file
12
examples/pods/pod-babashka-hsqldb/README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# pod-babashka-hsqldb
|
||||||
|
|
||||||
|
## Compile
|
||||||
|
|
||||||
|
Run `./compile`
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
``` clojure
|
||||||
|
(babashka.pods/load-pod "examples/pods/pod-babashka-hsqldb/pod-babashka-hsqldb")
|
||||||
|
(pod.hsqldb/execute! "jdbc:hsqldb:mem:testdb;sql.syntax_mys=true" ["create table foo ( foo int );"])'
|
||||||
|
```
|
||||||
33
examples/pods/pod-babashka-hsqldb/compile
Executable file
33
examples/pods/pod-babashka-hsqldb/compile
Executable file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -xeo pipefail
|
||||||
|
|
||||||
|
if [ -z "$GRAALVM_HOME" ]; then
|
||||||
|
echo "Please set GRAALVM_HOME"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$GRAALVM_HOME/bin/gu" install native-image || true
|
||||||
|
export PATH=$GRAALVM_HOME/bin:$PATH
|
||||||
|
|
||||||
|
JAR=target/pod-babashka-hsqldb-0.0.1-SNAPSHOT-standalone.jar
|
||||||
|
|
||||||
|
lein do clean, uberjar
|
||||||
|
|
||||||
|
$GRAALVM_HOME/bin/native-image \
|
||||||
|
-jar $JAR \
|
||||||
|
-H:Name=pod-babashka-hsqldb \
|
||||||
|
-H:+ReportExceptionStackTraces \
|
||||||
|
-J-Dclojure.spec.skip-macros=true \
|
||||||
|
-J-Dclojure.compiler.direct-linking=true \
|
||||||
|
"-H:IncludeResources=SCI_VERSION" \
|
||||||
|
-H:ReflectionConfigurationFiles=reflection.json \
|
||||||
|
--initialize-at-run-time=java.lang.Math\$RandomNumberGeneratorHolder \
|
||||||
|
--initialize-at-build-time \
|
||||||
|
-H:Log=registerResource: \
|
||||||
|
--verbose \
|
||||||
|
--no-fallback \
|
||||||
|
--no-server \
|
||||||
|
--report-unsupported-elements-at-runtime \
|
||||||
|
"-H:IncludeResources=org/hsqldb/.*\.properties" "-H:IncludeResources=org/hsqldb/.*\.sql" \
|
||||||
|
"-J-Xmx4500m"
|
||||||
BIN
examples/pods/pod-babashka-hsqldb/pod-babashka-hsqldb
Executable file
BIN
examples/pods/pod-babashka-hsqldb/pod-babashka-hsqldb
Executable file
Binary file not shown.
22
examples/pods/pod-babashka-hsqldb/project.clj
Normal file
22
examples/pods/pod-babashka-hsqldb/project.clj
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
(defproject org.babashka/pod-babashka-hsqldb "0.0.1-SNAPSHOT"
|
||||||
|
:description "babashka"
|
||||||
|
:url "https://github.com/borkdude/babashka"
|
||||||
|
:scm {:name "git"
|
||||||
|
:url "https://github.com/borkdude/babashka"}
|
||||||
|
:license {:name "Eclipse Public License 1.0"
|
||||||
|
:url "http://opensource.org/licenses/eclipse-1.0.php"}
|
||||||
|
:source-paths ["src"]
|
||||||
|
:resource-paths ["resources"]
|
||||||
|
:dependencies [[org.clojure/clojure "1.10.2-alpha1"]
|
||||||
|
[org.hsqldb/hsqldb "2.4.0"]
|
||||||
|
[seancorfield/next.jdbc "1.0.424"]
|
||||||
|
[nrepl/bencode "1.1.0"]]
|
||||||
|
:profiles {:uberjar {:global-vars {*assert* false}
|
||||||
|
:jvm-opts ["-Dclojure.compiler.direct-linking=true"
|
||||||
|
"-Dclojure.spec.skip-macros=true"]
|
||||||
|
:aot :all
|
||||||
|
:main pod.babashka.hsqldb}}
|
||||||
|
:deploy-repositories [["clojars" {:url "https://clojars.org/repo"
|
||||||
|
:username :env/clojars_user
|
||||||
|
:password :env/clojars_pass
|
||||||
|
:sign-releases false}]])
|
||||||
15
examples/pods/pod-babashka-hsqldb/reflection.json
Normal file
15
examples/pods/pod-babashka-hsqldb/reflection.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[ {
|
||||||
|
"name" : "org.hsqldb.jdbcDriver"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"methods" : [{"name":"<init>", "parameterTypes":[ "org.hsqldb.Database"]} ],
|
||||||
|
"name" : "org.hsqldb.dbinfo.DatabaseInformationFull"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"methods" : [ {
|
||||||
|
"name" : "getBundle",
|
||||||
|
"parameterTypes" : [ "java.lang.String", "java.util.Locale", "java.lang.ClassLoader" ]
|
||||||
|
} ],
|
||||||
|
"name" : "java.util.ResourceBundle"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
(ns pod.babashka.hsqldb
|
||||||
|
(:refer-clojure :exclude [read read-string])
|
||||||
|
(:require [bencode.core :as bencode]
|
||||||
|
[clojure.edn :as edn]
|
||||||
|
[next.jdbc :as jdbc])
|
||||||
|
(:import [java.io PushbackInputStream])
|
||||||
|
(:gen-class))
|
||||||
|
|
||||||
|
(def stdin (PushbackInputStream. System/in))
|
||||||
|
|
||||||
|
(def lookup
|
||||||
|
{'pod.hsqldb/execute! jdbc/execute!})
|
||||||
|
|
||||||
|
(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 -main [& _args]
|
||||||
|
(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" "edn"
|
||||||
|
"namespaces" [{"name" "pod.hsqldb"
|
||||||
|
"vars" [{"name" "execute!"}]}]})
|
||||||
|
(recur))
|
||||||
|
:invoke (let [var (-> (get message "var")
|
||||||
|
read-string
|
||||||
|
symbol)
|
||||||
|
id (-> (get message "id")
|
||||||
|
read-string)
|
||||||
|
args (get message "args")
|
||||||
|
args (read-string args)
|
||||||
|
args (edn/read-string args)]
|
||||||
|
(write {"value" (pr-str (apply (lookup var) args))
|
||||||
|
"id" id
|
||||||
|
"status" ["done"]})
|
||||||
|
(recur))))))))
|
||||||
45
examples/pods/pod-babashka-hsqldb/test.clj
Normal file
45
examples/pods/pod-babashka-hsqldb/test.clj
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
(ns test
|
||||||
|
(:refer-clojure :exclude [read])
|
||||||
|
(:require [bencode.core :as bencode]
|
||||||
|
[clojure.edn :as edn])
|
||||||
|
#_(:import java.lang.ProcessBuilder$Redirect))
|
||||||
|
|
||||||
|
(defn write [stream v]
|
||||||
|
(bencode/write-bencode stream v)
|
||||||
|
(.flush stream))
|
||||||
|
|
||||||
|
(defn read [stream]
|
||||||
|
(bencode/read-bencode stream))
|
||||||
|
|
||||||
|
(defn bytes->string [^"[B" bytes]
|
||||||
|
(String. bytes))
|
||||||
|
|
||||||
|
(defn query [stream q]
|
||||||
|
(write stream {"op" "invoke"
|
||||||
|
"var" "hsqldb.jdbc/execute!"
|
||||||
|
"args" (pr-str ["jdbc:hsqldb:mem:testdb;sql.syntax_mys=true" q])}))
|
||||||
|
|
||||||
|
(let [pb (ProcessBuilder. #_["lein" "run" "-m" "org.babashka.hsqldb"]
|
||||||
|
["./hsqldb-babashka-plugin"])
|
||||||
|
_ (.redirectErrorStream pb true)
|
||||||
|
;; _ (.redirectOutput pb ProcessBuilder$Redirect/INHERIT)
|
||||||
|
p (.start pb)
|
||||||
|
stdin (.getOutputStream p)
|
||||||
|
stdout (.getInputStream p)
|
||||||
|
stdout (java.io.PushbackInputStream. stdout)]
|
||||||
|
(write stdin {"op" "describe"})
|
||||||
|
(let [reply (read stdout)]
|
||||||
|
(println "format:" (String. (get reply "format")))
|
||||||
|
(println "vars:" (mapv bytes->string (get reply "vars")))) ;;=> edn
|
||||||
|
|
||||||
|
(query stdin ["create table foo ( foo int );"])
|
||||||
|
(let [reply (read stdout)]
|
||||||
|
(println "reply:" (edn/read-string (String. (get reply "value"))))) ;;=> [{:next.jdbc/update-count 0}]
|
||||||
|
|
||||||
|
(query stdin ["insert into foo values ( 1, 2, 3);"])
|
||||||
|
(let [reply (read stdout)]
|
||||||
|
(println "reply:" (edn/read-string (String. (get reply "value"))))) ;;=> [{:next.jdbc/update-count 3}]
|
||||||
|
|
||||||
|
(query stdin ["select * from foo;"])
|
||||||
|
(let [reply (read stdout)]
|
||||||
|
(println "reply:" (edn/read-string (String. (get reply "value")))))) ;=> [{:FOO/FOO 1} {:FOO/FOO 2} {:FOO/FOO 3}]
|
||||||
20
examples/pods/pod-lispyclouds-sqlite/README.md
Normal file
20
examples/pods/pod-lispyclouds-sqlite/README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# pod-lispyclouds-sqlite
|
||||||
|
|
||||||
|
To run this:
|
||||||
|
|
||||||
|
- Install python3 and sqlite3
|
||||||
|
- Create a virtualenv: `python3 -m venv ~/.virtualenvs/babashka`
|
||||||
|
- Switch to it: `source ~/.virtualenvs/babashka/bin/activate`
|
||||||
|
- Run: `pip install bcoding` to install the bencode lib
|
||||||
|
- Create a new db: `sqlite3 /tmp/babashka.db "create table foo (foo int);"`
|
||||||
|
|
||||||
|
Then run as pod:
|
||||||
|
|
||||||
|
``` clojure
|
||||||
|
(babashka.pods/load-pod ["examples/pods/pod-lispyclouds-sqlite/pod-lispyclouds-sqlite.py"])
|
||||||
|
(require '[pod.lispy-clouds.sqlite :as sqlite])
|
||||||
|
(sqlite/execute! "create table if not exists foo ( int foo )")
|
||||||
|
(sqlite/execute! "delete from foo")
|
||||||
|
(sqlite/execute! "insert into foo values (1), (2)")
|
||||||
|
(sqlite/execute! "select * from foo") ;;=> ([1] [2])
|
||||||
|
```
|
||||||
62
examples/pods/pod-lispyclouds-sqlite/pod-lispyclouds-sqlite.py
Executable file
62
examples/pods/pod-lispyclouds-sqlite/pod-lispyclouds-sqlite.py
Executable file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from bcoding import bencode, bdecode
|
||||||
|
|
||||||
|
|
||||||
|
def read():
|
||||||
|
return dict(bdecode(sys.stdin.buffer))
|
||||||
|
|
||||||
|
|
||||||
|
def write(obj):
|
||||||
|
sys.stdout.buffer.write(bencode(obj))
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def debug(*msg):
|
||||||
|
with open("/tmp/debug.log", "a") as f:
|
||||||
|
f.write(str(msg) + "\n")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
while True:
|
||||||
|
msg = read()
|
||||||
|
debug("msg", msg)
|
||||||
|
|
||||||
|
op = msg["op"]
|
||||||
|
|
||||||
|
if op == "describe":
|
||||||
|
write(
|
||||||
|
{
|
||||||
|
"format": "json",
|
||||||
|
"namespaces": [{"name": "pod.lispy-clouds.sqlite",
|
||||||
|
"vars": [{"name": "execute!"}]}]}
|
||||||
|
)
|
||||||
|
elif op == "invoke":
|
||||||
|
var = msg["var"]
|
||||||
|
id = msg["id"]
|
||||||
|
args = json.loads(msg["args"])
|
||||||
|
debug(args)
|
||||||
|
conn = sqlite3.connect("/tmp/babashka.db")
|
||||||
|
c = conn.cursor()
|
||||||
|
|
||||||
|
result = None
|
||||||
|
|
||||||
|
if var == "pod.lispy-clouds.sqlite/execute!":
|
||||||
|
try:
|
||||||
|
result = c.execute(*args)
|
||||||
|
except Exception as e:
|
||||||
|
debug(e)
|
||||||
|
|
||||||
|
value = json.dumps(result.fetchall())
|
||||||
|
debug("value", value)
|
||||||
|
|
||||||
|
write({"value": value, "id": id, "status": ["done"]})
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
:source-paths ["src" "sci/src" "babashka.curl/src"]
|
:source-paths ["src" "sci/src" "babashka.curl/src"]
|
||||||
;; for debugging Reflector.java code:
|
;; for debugging Reflector.java code:
|
||||||
;; :java-source-paths ["sci/reflector/src-java"]
|
;; :java-source-paths ["sci/reflector/src-java"]
|
||||||
|
:java-source-paths ["src-java"]
|
||||||
:resource-paths ["resources" "sci/resources"]
|
:resource-paths ["resources" "sci/resources"]
|
||||||
:dependencies [[org.clojure/clojure "1.10.2-alpha1"]
|
:dependencies [[org.clojure/clojure "1.10.2-alpha1"]
|
||||||
[org.clojure/tools.reader "1.3.2"]
|
[org.clojure/tools.reader "1.3.2"]
|
||||||
|
|
|
||||||
2
sci
2
sci
|
|
@ -1 +1 @@
|
||||||
Subproject commit 438ec15798f319f232d789b74b04ac25f15d540b
|
Subproject commit 806cf61eeff31869727e205a8f1a7021a0ea5b49
|
||||||
|
|
@ -15,8 +15,10 @@
|
||||||
'generate-smile (copy-var json/generate-smile tns)
|
'generate-smile (copy-var json/generate-smile tns)
|
||||||
'decode (copy-var json/decode tns)
|
'decode (copy-var json/decode tns)
|
||||||
'parse-string (copy-var json/parse-string tns)
|
'parse-string (copy-var json/parse-string tns)
|
||||||
|
'parse-string-strict (copy-var json/parse-string-strict tns)
|
||||||
'parse-smile (copy-var json/parse-smile tns)
|
'parse-smile (copy-var json/parse-smile tns)
|
||||||
'parse-stream (copy-var json/parse-stream tns)
|
'parse-stream (copy-var json/parse-stream tns)
|
||||||
|
'parse-stream-strict (copy-var json/parse-stream-strict tns)
|
||||||
'parsed-seq (copy-var json/parsed-seq tns)
|
'parsed-seq (copy-var json/parsed-seq tns)
|
||||||
'parsed-smile-seq (copy-var json/parsed-smile-seq tns)
|
'parsed-smile-seq (copy-var json/parsed-smile-seq tns)
|
||||||
'decode-smile (copy-var json/decode-smile tns)})
|
'decode-smile (copy-var json/decode-smile tns)})
|
||||||
|
|
|
||||||
153
src/babashka/impl/pods.clj
Normal file
153
src/babashka/impl/pods.clj
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
(ns babashka.impl.pods
|
||||||
|
{:no-doc true}
|
||||||
|
(:refer-clojure :exclude [read])
|
||||||
|
(:require [babashka.impl.bencode.core :as bencode]
|
||||||
|
[cheshire.core :as cheshire]
|
||||||
|
[clojure.core.async :as async]
|
||||||
|
[clojure.edn :as edn]
|
||||||
|
[sci.core :as sci]))
|
||||||
|
|
||||||
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
|
(defn add-shutdown-hook! [^Runnable f]
|
||||||
|
(-> (Runtime/getRuntime)
|
||||||
|
(.addShutdownHook (Thread. f))))
|
||||||
|
|
||||||
|
(defn write [^java.io.OutputStream stream v]
|
||||||
|
(locking stream
|
||||||
|
(bencode/write-bencode stream v)
|
||||||
|
(.flush stream)))
|
||||||
|
|
||||||
|
(defn read [stream]
|
||||||
|
(bencode/read-bencode stream))
|
||||||
|
|
||||||
|
(defn bytes->string [^"[B" bytes]
|
||||||
|
(String. bytes))
|
||||||
|
|
||||||
|
(defn get-string [m k]
|
||||||
|
(-> (get m k)
|
||||||
|
bytes->string))
|
||||||
|
|
||||||
|
(defn processor [_ctx pod]
|
||||||
|
(let [stdout (:stdout pod)
|
||||||
|
format (:format pod)
|
||||||
|
chans (:chans pod)
|
||||||
|
read-fn (case format
|
||||||
|
:edn edn/read-string
|
||||||
|
:json #(cheshire/parse-string-strict % true))]
|
||||||
|
(try
|
||||||
|
(loop []
|
||||||
|
(let [reply (read stdout)
|
||||||
|
id (get reply "id")
|
||||||
|
id (bytes->string id)
|
||||||
|
value* (find reply "value")
|
||||||
|
value (some-> value*
|
||||||
|
second
|
||||||
|
bytes->string
|
||||||
|
read-fn)
|
||||||
|
status (get reply "status")
|
||||||
|
status (set (map (comp keyword bytes->string) status))
|
||||||
|
done? (contains? status :done)
|
||||||
|
error? (contains? status :error)
|
||||||
|
value (if error?
|
||||||
|
(let [message (or (some-> (get reply "ex-message")
|
||||||
|
bytes->string)
|
||||||
|
"")
|
||||||
|
data (or (some-> (get reply "ex-data")
|
||||||
|
bytes->string
|
||||||
|
read-fn)
|
||||||
|
{})]
|
||||||
|
(ex-info message data))
|
||||||
|
value)
|
||||||
|
chan (get @chans id)
|
||||||
|
out (some-> (get reply "out")
|
||||||
|
bytes->string)
|
||||||
|
err (some-> (get reply "err")
|
||||||
|
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]
|
||||||
|
(println err))))
|
||||||
|
(recur))
|
||||||
|
(catch Exception e
|
||||||
|
(binding [*out* @sci/err]
|
||||||
|
(prn e))))))
|
||||||
|
|
||||||
|
(defn invoke [pod pod-var args async?]
|
||||||
|
(let [stream (:stdin pod)
|
||||||
|
format (:format pod)
|
||||||
|
chans (:chans pod)
|
||||||
|
write-fn (case format
|
||||||
|
:edn pr-str
|
||||||
|
:json cheshire/generate-string)
|
||||||
|
id (str (java.util.UUID/randomUUID))
|
||||||
|
chan (async/chan)
|
||||||
|
_ (swap! chans assoc id chan)
|
||||||
|
_ (write stream {"id" id
|
||||||
|
"op" "invoke"
|
||||||
|
"var" (str pod-var)
|
||||||
|
"args" (write-fn args)})]
|
||||||
|
(if async? chan ;; TODO: https://blog.jakubholy.net/2019/core-async-error-handling/
|
||||||
|
(let [v (async/<!! chan)]
|
||||||
|
(if (instance? Throwable v)
|
||||||
|
(throw v)
|
||||||
|
v)))))
|
||||||
|
|
||||||
|
(defn load-pod
|
||||||
|
([ctx pod-spec] (load-pod ctx pod-spec nil))
|
||||||
|
([ctx pod-spec _opts]
|
||||||
|
(let [pod-spec (if (string? pod-spec) [pod-spec] pod-spec)
|
||||||
|
pb (ProcessBuilder. ^java.util.List pod-spec)
|
||||||
|
_ (.redirectErrorStream pb true)
|
||||||
|
p (.start pb)
|
||||||
|
stdin (.getOutputStream p)
|
||||||
|
stdout (.getInputStream p)
|
||||||
|
stdout (java.io.PushbackInputStream. stdout)
|
||||||
|
_ (add-shutdown-hook! #(.destroy p))
|
||||||
|
_ (write stdin {"op" "describe"})
|
||||||
|
reply (read stdout)
|
||||||
|
format (-> (get reply "format") bytes->string keyword)
|
||||||
|
pod {:process p
|
||||||
|
:pod-spec pod-spec
|
||||||
|
:stdin stdin
|
||||||
|
:stdout stdout
|
||||||
|
:chans (atom {})
|
||||||
|
:format format}
|
||||||
|
pod-namespaces (get reply "namespaces")
|
||||||
|
vars-fn (fn [ns-name-str vars]
|
||||||
|
(reduce
|
||||||
|
(fn [m var]
|
||||||
|
(let [name (get-string var "name")
|
||||||
|
async? (some-> (get var "async")
|
||||||
|
bytes->string
|
||||||
|
#(Boolean/parseBoolean %))
|
||||||
|
name-sym (symbol name)
|
||||||
|
sym (symbol ns-name-str name)]
|
||||||
|
(assoc m name-sym (fn [& args]
|
||||||
|
(let [res (invoke pod sym args async?)]
|
||||||
|
res)))))
|
||||||
|
{}
|
||||||
|
vars))
|
||||||
|
env (:env ctx)]
|
||||||
|
(swap! env
|
||||||
|
(fn [env]
|
||||||
|
(let [namespaces (:namespaces env)
|
||||||
|
namespaces
|
||||||
|
(reduce (fn [namespaces namespace]
|
||||||
|
(let [name-str (-> namespace (get "name") bytes->string)
|
||||||
|
name-sym (symbol name-str)
|
||||||
|
vars (get namespace "vars")
|
||||||
|
vars (vars-fn name-str vars)]
|
||||||
|
(assoc namespaces name-sym vars)))
|
||||||
|
namespaces
|
||||||
|
pod-namespaces)]
|
||||||
|
(assoc env :namespaces namespaces))))
|
||||||
|
(sci/future (processor ctx pod))
|
||||||
|
;; TODO: we could return the entire describe map here
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
(def pods-namespace
|
||||||
|
{'load-pod (with-meta load-pod
|
||||||
|
{:sci.impl/op :needs-ctx})})
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
[babashka.impl.curl :refer [curl-namespace]]
|
[babashka.impl.curl :refer [curl-namespace]]
|
||||||
[babashka.impl.features :as features]
|
[babashka.impl.features :as features]
|
||||||
[babashka.impl.nrepl-server :as nrepl-server]
|
[babashka.impl.nrepl-server :as nrepl-server]
|
||||||
|
[babashka.impl.pods :as pods]
|
||||||
[babashka.impl.repl :as repl]
|
[babashka.impl.repl :as repl]
|
||||||
[babashka.impl.socket-repl :as socket-repl]
|
[babashka.impl.socket-repl :as socket-repl]
|
||||||
[babashka.impl.test :as t]
|
[babashka.impl.test :as t]
|
||||||
|
|
@ -349,6 +350,7 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that
|
||||||
'babashka.classpath {'add-classpath add-classpath*}
|
'babashka.classpath {'add-classpath add-classpath*}
|
||||||
'clojure.pprint pprint-namespace
|
'clojure.pprint pprint-namespace
|
||||||
'babashka.curl curl-namespace
|
'babashka.curl curl-namespace
|
||||||
|
'babashka.pods pods/pods-namespace
|
||||||
'bencode.core bencode-namespace}
|
'bencode.core bencode-namespace}
|
||||||
features/xml? (assoc 'clojure.data.xml @(resolve 'babashka.impl.xml/xml-namespace))
|
features/xml? (assoc 'clojure.data.xml @(resolve 'babashka.impl.xml/xml-namespace))
|
||||||
features/yaml? (assoc 'clj-yaml.core @(resolve 'babashka.impl.yaml/yaml-namespace))
|
features/yaml? (assoc 'clj-yaml.core @(resolve 'babashka.impl.yaml/yaml-namespace))
|
||||||
|
|
|
||||||
146
test-resources/pod.clj
Normal file
146
test-resources/pod.clj
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
(ns pod
|
||||||
|
(:refer-clojure :exclude [read read-string])
|
||||||
|
(:require [babashka.pods :as pods]
|
||||||
|
[bencode.core :as bencode]
|
||||||
|
[cheshire.core :as cheshire]
|
||||||
|
[clojure.core.async :as async]
|
||||||
|
[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
|
||||||
|
;; TODO:
|
||||||
|
;; group by namespace
|
||||||
|
: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"}]}]})
|
||||||
|
(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)))))))))
|
||||||
|
|
||||||
|
(let [cli-args (set *command-line-args*)]
|
||||||
|
(if (contains? cli-args "--run-as-pod")
|
||||||
|
(do (debug "running pod with cli args" cli-args)
|
||||||
|
(run-pod cli-args))
|
||||||
|
(let [native? (contains? cli-args "--native")]
|
||||||
|
(pods/load-pod (if native?
|
||||||
|
(into ["./bb" "test-resources/pod.clj" "--run-as-pod"] cli-args)
|
||||||
|
(into ["lein" "bb" "test-resources/pod.clj" "--run-as-pod"] cli-args)))
|
||||||
|
(require '[pod.test-pod])
|
||||||
|
(if (contains? cli-args "--json")
|
||||||
|
(do
|
||||||
|
(debug "Running JSON test")
|
||||||
|
(prn ((resolve 'pod.test-pod/assoc) {:a 1} :b 2)))
|
||||||
|
(do
|
||||||
|
(debug "Running synchronous add test")
|
||||||
|
(prn ((resolve 'pod.test-pod/add-sync) 1 2 3))
|
||||||
|
(debug "Running async stream test")
|
||||||
|
(let [chan ((resolve 'pod.test-pod/range-stream) 1 10)]
|
||||||
|
(loop []
|
||||||
|
(when-let [x (async/<!! chan)]
|
||||||
|
(debug "Received" x)
|
||||||
|
(prn x)
|
||||||
|
(recur))))
|
||||||
|
(debug "Running exception test")
|
||||||
|
(prn (try ((resolve 'pod.test-pod/error) 1 2 3)
|
||||||
|
(catch clojure.lang.ExceptionInfo e
|
||||||
|
(str (ex-message e) " / " (ex-data e)))))
|
||||||
|
(debug "Running print test")
|
||||||
|
((resolve 'pod.test-pod/print) "hello" "print" "this" "debugging" "message")
|
||||||
|
(debug "Running print-err test")
|
||||||
|
((resolve 'pod.test-pod/print-err) "hello" "print" "this" "error"))))))
|
||||||
21
test/babashka/pod_test.clj
Normal file
21
test/babashka/pod_test.clj
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
(ns babashka.pod-test
|
||||||
|
(:require [babashka.test-utils :as tu]
|
||||||
|
[clojure.edn :as edn]
|
||||||
|
[clojure.test :as t :refer [deftest is]]))
|
||||||
|
|
||||||
|
(deftest pod-test
|
||||||
|
(let [native? tu/native?
|
||||||
|
sw (java.io.StringWriter.)
|
||||||
|
res (apply tu/bb {:err sw}
|
||||||
|
(cond-> ["-f" "test-resources/pod.clj"]
|
||||||
|
native?
|
||||||
|
(conj "--native")))
|
||||||
|
err (str sw)]
|
||||||
|
(is (= "6\n1\n2\n3\n4\n5\n6\n7\n8\n9\n\"Illegal arguments / {:args (1 2 3)}\"\n(\"hello\" \"print\" \"this\" \"debugging\" \"message\")\n" res))
|
||||||
|
(when-not tu/native?
|
||||||
|
(is (= "(\"hello\" \"print\" \"this\" \"error\")\n" err)))
|
||||||
|
(is (= {:a 1 :b 2}
|
||||||
|
(edn/read-string
|
||||||
|
(apply tu/bb nil (cond-> ["-f" "test-resources/pod.clj" "--json"]
|
||||||
|
native?
|
||||||
|
(conj "--native"))))))))
|
||||||
|
|
@ -7,24 +7,25 @@
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
||||||
(defn bb-jvm [input & args]
|
(defn bb-jvm [input-or-opts & args]
|
||||||
(reset! main/cp-state nil)
|
(reset! main/cp-state nil)
|
||||||
(let [os (java.io.StringWriter.)
|
(let [os (java.io.StringWriter.)
|
||||||
es (java.io.StringWriter.)
|
es (if-let [err (:err input-or-opts)]
|
||||||
is (when input
|
err (java.io.StringWriter.))
|
||||||
(java.io.StringReader. input))
|
is (when (string? input-or-opts)
|
||||||
|
(java.io.StringReader. input-or-opts))
|
||||||
bindings-map (cond-> {sci/out os
|
bindings-map (cond-> {sci/out os
|
||||||
sci/err es}
|
sci/err es}
|
||||||
is (assoc sci/in is))]
|
is (assoc sci/in is))]
|
||||||
(try
|
(try
|
||||||
(when input (vars/bindRoot sci/in is))
|
(when (string? input-or-opts) (vars/bindRoot sci/in is))
|
||||||
(vars/bindRoot sci/out os)
|
(vars/bindRoot sci/out os)
|
||||||
(vars/bindRoot sci/err es)
|
(vars/bindRoot sci/err es)
|
||||||
(sci/with-bindings bindings-map
|
(sci/with-bindings bindings-map
|
||||||
(let [res (binding [*out* os
|
(let [res (binding [*out* os
|
||||||
*err* es]
|
*err* es]
|
||||||
(if input
|
(if (string? input-or-opts)
|
||||||
(with-in-str input (apply main/main args))
|
(with-in-str input-or-opts (apply main/main args))
|
||||||
(apply main/main args)))]
|
(apply main/main args)))]
|
||||||
(if (zero? res)
|
(if (zero? res)
|
||||||
(str os)
|
(str os)
|
||||||
|
|
@ -32,7 +33,7 @@
|
||||||
{:stdout (str os)
|
{:stdout (str os)
|
||||||
:stderr (str es)})))))
|
:stderr (str es)})))))
|
||||||
(finally
|
(finally
|
||||||
(when input (vars/bindRoot sci/in *in*))
|
(when (string? input-or-opts) (vars/bindRoot sci/in *in*))
|
||||||
(vars/bindRoot sci/out *out*)
|
(vars/bindRoot sci/out *out*)
|
||||||
(vars/bindRoot sci/err *err*)))))
|
(vars/bindRoot sci/err *err*)))))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue