[#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
|
||||
/reports
|
||||
*.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"]
|
||||
;; 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
2
sci
|
|
@ -1 +1 @@
|
|||
Subproject commit 438ec15798f319f232d789b74b04ac25f15d540b
|
||||
Subproject commit 806cf61eeff31869727e205a8f1a7021a0ea5b49
|
||||
|
|
@ -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
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.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
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)
|
||||
|
||||
(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*)))))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue