This commit is contained in:
Michiel Borkent 2020-05-06 21:14:14 +02:00 committed by GitHub
parent d5f9a1914e
commit eb4f25f5c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 1398 additions and 9 deletions

2
.gitignore vendored
View file

@ -20,3 +20,5 @@ pom.xml.asc
/tmp
/reports
*.dylib
*.log
org_babashka*.h

3
doc/pods.md Normal file
View file

@ -0,0 +1,3 @@
# Pods
Pods are commmand line programs that babashka can communicate with via stdin and stdout.

View file

@ -0,0 +1 @@
/target

View 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",
]

View 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"

View 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"]
```

View 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);
}
}

View 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)
}
}
}

View 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

View 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 );"])'
```

View 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"

Binary file not shown.

View 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}]])

View 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"
}
]

View file

@ -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))))))))

View 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}]

View 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])
```

View 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()

View file

@ -10,6 +10,7 @@
:source-paths ["src" "sci/src" "babashka.curl/src"]
;; for debugging Reflector.java code:
;; :java-source-paths ["sci/reflector/src-java"]
:java-source-paths ["src-java"]
:resource-paths ["resources" "sci/resources"]
:dependencies [[org.clojure/clojure "1.10.2-alpha1"]
[org.clojure/tools.reader "1.3.2"]

2
sci

@ -1 +1 @@
Subproject commit 438ec15798f319f232d789b74b04ac25f15d540b
Subproject commit 806cf61eeff31869727e205a8f1a7021a0ea5b49

View file

@ -15,8 +15,10 @@
'generate-smile (copy-var json/generate-smile tns)
'decode (copy-var json/decode 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-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-smile-seq (copy-var json/parsed-smile-seq tns)
'decode-smile (copy-var json/decode-smile tns)})

153
src/babashka/impl/pods.clj Normal file
View 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})})

View file

@ -15,6 +15,7 @@
[babashka.impl.curl :refer [curl-namespace]]
[babashka.impl.features :as features]
[babashka.impl.nrepl-server :as nrepl-server]
[babashka.impl.pods :as pods]
[babashka.impl.repl :as repl]
[babashka.impl.socket-repl :as socket-repl]
[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*}
'clojure.pprint pprint-namespace
'babashka.curl curl-namespace
'babashka.pods pods/pods-namespace
'bencode.core bencode-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))

146
test-resources/pod.clj Normal file
View 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"))))))

View 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"))))))))

View file

@ -7,24 +7,25 @@
(set! *warn-on-reflection* true)
(defn bb-jvm [input & args]
(defn bb-jvm [input-or-opts & args]
(reset! main/cp-state nil)
(let [os (java.io.StringWriter.)
es (java.io.StringWriter.)
is (when input
(java.io.StringReader. input))
es (if-let [err (:err input-or-opts)]
err (java.io.StringWriter.))
is (when (string? input-or-opts)
(java.io.StringReader. input-or-opts))
bindings-map (cond-> {sci/out os
sci/err es}
is (assoc sci/in is))]
(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/err es)
(sci/with-bindings bindings-map
(let [res (binding [*out* os
*err* es]
(if input
(with-in-str input (apply main/main args))
(if (string? input-or-opts)
(with-in-str input-or-opts (apply main/main args))
(apply main/main args)))]
(if (zero? res)
(str os)
@ -32,7 +33,7 @@
{:stdout (str os)
:stderr (str es)})))))
(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/err *err*)))))