Move filewatcher to own repo (#423)

This commit is contained in:
Michiel Borkent 2020-05-10 13:40:03 +02:00 committed by GitHub
parent 4e2bed4131
commit ea220f36a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 3 additions and 784 deletions

View file

@ -19,7 +19,9 @@ Currently the following pods are available:
- [clj-kondo](https://github.com/borkdude/clj-kondo/#babashka-pod): a Clojure
linter
- [pod-babashka-hsqldb](https://github.com/borkdude/pod-babashka-hsqldb): a pod
- [pod-babashka-filewatcher](https://github.com/babashka/pod-babashka-hsqldb): a
filewatcher pod based on Rust notify.
- [pod-babashka-hsqldb](https://github.com/babashka/pod-babashka-hsqldb): a pod
that allows you to create and fire queries at a
[HSQLDB](http://www.hsqldb.org/) database.
@ -39,9 +41,6 @@ can be found [here](../examples/pods):
allows you to create and fire queries at a [sqlite](https://www.sqlite.org/)
database. Implemented in Python.
- [pod-babashka-filewatcher](../examples/pods/pod-babashka-filewatcher): a
filewatcher pod. Implemented in Rust.
### Naming
When choosing a name for your pod, we suggest the following naming scheme:

View file

@ -1 +0,0 @@
/target

View file

@ -1,292 +0,0 @@
# 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

@ -1,12 +0,0 @@
[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

@ -1,18 +0,0 @@
# pod-babashka-filewatcher
## Compile
```
$ cargo build --release
```
## Run
``` clojure
(babashka.pods/load-pod "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

@ -1,320 +0,0 @@
// 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

@ -1,137 +0,0 @@
// 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)
}
}
}