Last active
June 30, 2018 13:52
-
-
Save alaingilbert/4ff333b8b2257bb98d5b24aecdcd185c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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