Skip to content

Instantly share code, notes, and snippets.

@alaingilbert
Last active June 30, 2018 13:52
Show Gist options
  • Save alaingilbert/4ff333b8b2257bb98d5b24aecdcd185c to your computer and use it in GitHub Desktop.
Save alaingilbert/4ff333b8b2257bb98d5b24aecdcd185c to your computer and use it in GitHub Desktop.
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import re
import sublime
import sublime_plugin
import SublimeREPL.sublimerepl
import SublimeREPL.text_transfer
import os
import sys
import socket
import threading
try:
from urlparse import urlparse, ParseResult
except ImportError:
from urllib.parse import urlparse, ParseResult
# Some code so we can use different features without worrying about versions.
PY2 = sys.version_info[0] == 2
if not PY2:
text_type = str
string_types = (str, bytes)
unichr = chr
else:
text_type = unicode
string_types = (str, unicode)
unichr = unichr
class BencodeIO(object):
def __init__(self, file):
self._file = file
def read(self):
return _read_datum(self._file)
def __iter__(self):
return self
def next(self):
v = self.read()
if not v:
raise StopIteration
return v
def __next__(self):
# In Python3, __next__ it is an own special class.
v = self.read()
if not v:
raise StopIteration
return v
def write(self, v):
return _write_datum(v, self._file)
def flush(self):
if self._file.flush:
self._file.flush()
def close(self):
self._file.close()
def _bencode_connect(uri):
s = socket.create_connection(uri.netloc.split(":"))
# TODO I don't think .close() will propagate to the socket automatically...
f = s.makefile('rw')
return BencodeIO(f)
# others can add in implementations here
_connect_fns = {"nrepl": _bencode_connect}
def connect(uri):
"""
Connects to an nREPL endpoint identified by the given URL/URI. Valid
examples include:
nrepl://192.168.0.12:7889
telnet://localhost:5000
http://your-app-name.heroku.com/repl
This fn delegates to another looked up in that dispatches on the scheme of
the URI provided (which can be a string or java.net.URI). By default, only
`nrepl` (corresponding to using the default bencode transport) is
supported. Alternative implementations may add support for other schemes,
such as http/https, JMX, various message queues, etc.
"""
#
uri = uri if isinstance(uri, ParseResult) else urlparse(uri)
if not uri.scheme:
raise ValueError("uri has no scheme: " + uri)
f = _connect_fns.get(uri.scheme.lower(), None)
if not f:
err = "No connect function registered for scheme `%s`" % uri.scheme
raise Exception(err)
return f(uri)
c = connect("nrepl://localhost:7888")
class RefreshNamespacesInReplCommand(SublimeREPL.text_transfer.ReplTransferCurrent):
def run(self, edit):
ns = re.sub("ns\s*", "", self.view.substr(self.view.find("ns\s*\S+",0)))
text = "(require '%s :reload)" % ns
try:
c.write({"op": "eval", "code": text})
except:
c = connect("nrepl://localhost:7888")
c.write({"op": "eval", "code": text})
c.read()
c.read()
text = "(curbside.api.runtime/stop!)"
c.write({"op": "eval", "code": text})
c.read()
c.read()
text = "(curbside.api.runtime/start!)"
c.write({"op": "eval", "code": text})
c.read()
c.read()
def _read_byte(s):
return s.read(1)
def _read_int(s, terminator=None, init_data=None):
int_chrs = init_data or []
while True:
c = _read_byte(s)
if not c.isdigit() or c == terminator or not c:
break
else:
int_chrs.append(c)
return int(''.join(int_chrs))
def _read_bytes(s, n):
data = StringIO()
cnt = 0
while cnt < n:
m = s.read(n - cnt)
if not m:
raise Exception("Invalid bytestring, unexpected end of input.")
data.write(m)
cnt += len(m)
data.flush()
# Taking into account that Python3 can't decode strings
try:
ret = data.getvalue().decode("UTF-8")
except AttributeError:
ret = data.getvalue()
return ret
def _read_delimiter(s):
d = _read_byte(s)
if d.isdigit():
d = _read_int(s, ":", [d])
return d
def _read_list(s):
data = []
while True:
datum = _read_datum(s)
if not datum:
break
data.append(datum)
return data
def _read_map(s):
i = iter(_read_list(s))
return dict(zip(i, i))
_read_fns = {"i": _read_int,
"l": _read_list,
"d": _read_map,
"e": lambda _: None,
# EOF
None: lambda _: None}
def _read_datum(s):
delim = _read_delimiter(s)
if delim:
return _read_fns.get(delim, lambda s: _read_bytes(s, delim))(s)
def _write_datum(x, out):
if isinstance(x, string_types):
# x = x.encode("UTF-8")
# TODO revisit encodings, this is surely not right. Python
# (2.x, anyway) conflates bytes and strings, but 3.x does not...
out.write(str(len(x)))
out.write(":")
out.write(x)
elif isinstance(x, int):
out.write("i")
out.write(str(x))
out.write("e")
elif isinstance(x, (list, tuple)):
out.write("l")
for v in x:
_write_datum(v, out)
out.write("e")
elif isinstance(x, dict):
out.write("d")
for k, v in x.items():
_write_datum(k, out)
_write_datum(v, out)
out.write("e")
out.flush()
def encode(v):
"bencodes the given value, may be a string, integer, list, or dict."
s = StringIO()
_write_datum(v, s)
return s.getvalue()
def decode_file(file):
while True:
x = _read_datum(file)
if not x:
break
yield x
def decode(string):
"Generator that yields decoded values from the input string."
return decode_file(StringIO(string))
def _match_criteria(criteria, msg):
for k, v in criteria.items():
mv = msg.get(k, None)
if isinstance(v, set):
if mv not in v:
return False
elif not v and mv:
pass
elif not mv or v != mv:
return False
return True
class WatchableConnection(object):
def __init__(self, IO):
"""
Create a new WatchableConnection with an nREPL message transport
supporting `read()` and `write()` methods that return and accept nREPL
messages, e.g. bencode.BencodeIO.
"""
self._IO = IO
self._watches = {}
self._watches_lock = threading.RLock()
class Monitor(threading.Thread):
def run(_):
watches = None
for incoming in self._IO:
with self._watches_lock:
watches = dict(self._watches)
for key, (pred, callback) in watches.items():
if pred(incoming):
callback(incoming, self, key)
self._thread = Monitor()
self._thread.daemon = True
self._thread.start()
def close(self):
self._IO.close()
def send(self, message):
"Send an nREPL message."
self._IO.write(message)
def unwatch(self, key):
"Removes the watch previously registered with [key]."
with self._watches_lock:
self._watches.pop(key, None)
def watch(self, key, criteria, callback):
"""
Registers a new watch under [key] (which can be used with `unwatch()`
to remove the watch) that filters messages using [criteria] (may be a
predicate or a 'criteria dict' [see the README for more info there]).
Matching messages are passed to [callback], which must accept three
arguments: the matched incoming message, this instance of
`WatchableConnection`, and the key under which the watch was
registered.
"""
if hasattr(criteria, '__call__'):
pred = criteria
else:
pred = lambda incoming: _match_criteria(criteria, incoming)
with self._watches_lock:
self._watches[key] = (pred, callback)
// Key bindings user:
{ "keys": ["alt+super+r"], "command": "refresh_namespaces_in_repl"},
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment