├── LICENSE ├── .gitignore ├── install.sh ├── kernel.json ├── dub.sdl ├── source ├── app.d └── jupyterd │ ├── conn.d │ ├── interpreter.d │ ├── message.d │ └── kernel.d ├── README.MD └── install.d /LICENSE: -------------------------------------------------------------------------------- 1 | Boost license 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Untitled*.ipynb 2 | .ipynb_checkpoints 3 | .dub 4 | dub.selections.json 5 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | sudo mkdir /usr/share/jupyter/kernels/d 2 | sudo cp ./kernel.json /usr/share/jupyter/kernels/d 3 | dub build 4 | sudo cp ./jupyterd /usr/sbin 5 | -------------------------------------------------------------------------------- /kernel.json: -------------------------------------------------------------------------------- 1 | { 2 | "argv": [ 3 | "jupyterd", 4 | "d", 5 | "{connection_file}" 6 | ], 7 | "display_name": "d", 8 | "language": "d" 9 | } -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "jupyterd" 2 | description "A Jupyter notebook kernel written in D" 3 | authors "Nicholas Wilson" "Laeeth Isharc" 4 | copyright "Copyright © 2018, Kaleidic Associates" 5 | license "Boost" 6 | dependency "zmqd" version="~>1.1.1" 7 | dependency "asdf" version="~>0.2.2" 8 | dependency "drepl" version="~>0.2.1" 9 | targetType "executable" 10 | lflags "-ldl" 11 | //dflags-ldc2 "-link-defaultlib-shared" 12 | sourcePath "source" 13 | 14 | -------------------------------------------------------------------------------- /source/app.d: -------------------------------------------------------------------------------- 1 | import juypterd.interpreter; 2 | import juypterd.kernel; 3 | import zmqd; 4 | import std.stdio; 5 | 6 | version(NoMain) {} 7 | else 8 | { 9 | int main(string[] args) 10 | { 11 | Interpreter i; 12 | 13 | switch(args[1]) 14 | { 15 | case "echo": 16 | i = new EchoInterpreter(); 17 | break; 18 | case "d": 19 | i = new DInterpreter(); 20 | break; 21 | default: 22 | return 1; 23 | } 24 | auto k = Kernel(i,/*connection string=*/args[2]); 25 | k.mainloop(); 26 | return 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/jupyterd/conn.d: -------------------------------------------------------------------------------- 1 | /// ConnectionInfo stores the contents of the kernel connection file created by Jupyter. 2 | module jupyterd.conn; 3 | 4 | import asdf.serialization : serializationKeys; 5 | 6 | struct ConnectionInfo 7 | { 8 | @serializationKeys("signature_scheme") 9 | string signatureScheme; 10 | 11 | @serializationKeys("transport") 12 | string transport; 13 | 14 | @serializationKeys("stdin_port") 15 | ushort stdinPort; 16 | 17 | @serializationKeys("control_port") 18 | ushort controlPort; 19 | 20 | @serializationKeys("iopub_port") 21 | ushort ioPubPort; 22 | 23 | @serializationKeys("hb_port") 24 | ushort hbPort; 25 | 26 | @serializationKeys("shell_port") 27 | ushort shellPort; 28 | 29 | @serializationKeys("key") 30 | string key; 31 | 32 | @serializationKeys("ip") 33 | string ip; 34 | 35 | string connectionString(ushort port) const 36 | { 37 | import std.format : format; 38 | return format!"%s://%s:%s"(transport,ip,port); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | JupyterD 2 | ======== 3 | 4 | ** This project has been replaced by Jupyter-Wire ** 5 | https://github.com/kaleidicassociates/jupyter-wire 6 | 7 | 8 | 9 | A Jupyter Notebook kernel written in D. 10 | 11 | Currently supports an echo "interpreter" and D Repl. 12 | Tested on Arch Linux; should work on other distributions and Windows with minor changes. 13 | 14 | To install run ./install.sh 15 | This copies the executable jupyterd to /usr/sbin (you need sudo) and makes a directory 16 | /usr/share/jupyter/kernels/d/ and copies the kernel.json file to this directory. Change the argv 17 | argument in kernel.json from d to echo if you wish to switch to the echo interpreter. 18 | 19 | Then just run `jupyter notebook` and create a new notebook using the d kernel. 20 | 21 | Wait for the kernel to come online - it can take a while (needs to be fixed). 22 | 23 | Kernel imports all of phobos using std.experimental.all. 24 | 25 | It's pre-alpha, just a bare proof of concept right now. 26 | 27 | The existing D repl was written for the commandline and probably you will encounter difficulties running it in a notebook 28 | environment. 29 | 30 | 31 | Other projects - see pydmagic by John Colvin 32 | 33 | 34 | 35 | To Do 36 | ===== 37 | * Make it work as you would expect a kernel to in any language 38 | * Add HTML and markdown table output to display arrays of structs or of dicts in a useful manner 39 | * Integrate with mir and other numeric libraries 40 | * Integrate with charting 41 | * Consider adding to Dlang tour and run.dlang.io when stable 42 | * Integrate with dpp 43 | * Integrate with dub 44 | 45 | 46 | Notes 47 | ===== 48 | * Existing kernel will allow you to return MIME images, HTML and Markdown inline 49 | -------------------------------------------------------------------------------- /source/jupyterd/interpreter.d: -------------------------------------------------------------------------------- 1 | module juypterd.interpreter; 2 | import juypterd.kernel; 3 | import drepl.interpreter : InterpreterResult, interpreter; 4 | struct LanguageInfo 5 | { 6 | string name; 7 | string languageVersion; 8 | string fileExtension; 9 | string mimeType; 10 | } 11 | 12 | interface Interpreter 13 | { 14 | InterpreterResult interpret(const(char)[] code); 15 | 16 | ref const(LanguageInfo) languageInfo(); 17 | 18 | } 19 | 20 | final class EchoInterpreter : Interpreter 21 | { 22 | LanguageInfo li = LanguageInfo("echo","1.0.0",".txt", "text/plain"); 23 | 24 | private import drepl.engines; 25 | 26 | typeof(interpreter(echoEngine())) intp; 27 | InterpreterResult last; 28 | 29 | this() 30 | { 31 | intp = interpreter(echoEngine()); 32 | } 33 | override InterpreterResult interpret(const(char)[] code) 34 | { 35 | return InterpreterResult(InterpreterResult.State.success,cast(string)code,""); 36 | //return intp.interpret(code); 37 | } 38 | 39 | override ref const(LanguageInfo) languageInfo() 40 | { 41 | return li; 42 | } 43 | } 44 | 45 | final class DInterpreter : Interpreter 46 | { 47 | LanguageInfo li = LanguageInfo("D","2.081.1",".d", "text/plain"); 48 | 49 | private import drepl.engines; 50 | 51 | typeof(interpreter(dmdEngine())) intp; 52 | InterpreterResult last; 53 | 54 | this() 55 | { 56 | intp = interpreter(dmdEngine()); 57 | intp.interpret(`import std.experimental.all;`); 58 | } 59 | override InterpreterResult interpret(const(char)[] code) 60 | { 61 | import std.string:splitLines, join; 62 | import std.algorithm; 63 | import std.array:array; 64 | import std.range:back; 65 | import std.stdio:stderr,writeln; 66 | InterpreterResult.State state; 67 | auto result = code.splitLines.map!(line=>intp.interpret(line)).array; 68 | auto success = (result.all!(line=>line.state != InterpreterResult.State.error)); 69 | if (!success) 70 | state = InterpreterResult.State.error; 71 | else 72 | state = ((result.length==0) || (result.back.state != InterpreterResult.State.incomplete) )? InterpreterResult.State.success : InterpreterResult.State.incomplete; 73 | auto errorOutput = result.map!(line=>line.stderr).join("\n"); 74 | auto stdOutput = result.map!(line=>line.stdout).join("\n"); 75 | stderr.writeln(state,stdOutput,errorOutput); 76 | return InterpreterResult(state,stdOutput,errorOutput); 77 | } 78 | 79 | override ref const(LanguageInfo) languageInfo() 80 | { 81 | return li; 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /install.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/rdmd 2 | 3 | import std.stdio; 4 | import std.getopt; 5 | import std.exception : enforce; 6 | import std.array : empty, front, popFront; 7 | import std.process : execute; 8 | 9 | string jdir = "/usr/local/bin/"; 10 | 11 | string getKernelPath() { 12 | import std.algorithm : splitter, startsWith; 13 | import std.string : strip, splitLines; 14 | 15 | auto jp = execute(["jupyter", "kernelspec", "list"]); 16 | enforce(jp.status == 0, "Failed to quere jupyter for the kernelspecs"); 17 | 18 | auto sout = jp.output.splitLines; 19 | 20 | /* transform output of form 21 | Available kernels: 22 | python2 /usr/local/share/jupyter/kernels/python2 23 | 24 | into: /usr/local/share/jupyter/kernels/ 25 | */ 26 | 27 | if(sout.startsWith("Available kernels:")) { 28 | sout.popFront(); 29 | } else { 30 | throw new Exception("No available kernels found"); 31 | } 32 | 33 | while(!sout.empty) { 34 | enum p = "python2"; 35 | string f = sout.front.strip(); 36 | if(f.startsWith(p)) { 37 | f = f[p.length .. $].strip(); 38 | f = f[0 .. $ - p.length]; 39 | return f; 40 | } 41 | sout.popFront(); 42 | } 43 | throw new Exception("python2 kernel path not found"); 44 | } 45 | 46 | void installJupyterd(const string kernelPath, const bool build) { 47 | import std.file : exists, mkdir, copy, isDir, isFile; 48 | 49 | const ddir = kernelPath ~ 'd'; 50 | if(!exists(ddir)) { 51 | mkdir(ddir); 52 | } 53 | 54 | enum k = "kernel.json"; 55 | enforce(isFile(k), "kernel.json not found in pwd"); 56 | auto g = execute(["cp", k, ddir]); 57 | 58 | enum j = "jupyterd"; 59 | if(!exists(j) || build) { 60 | auto e = execute(["dub", "build"]); 61 | writeln(e.output); 62 | } 63 | 64 | auto f = execute(["cp", j, jdir]); 65 | } 66 | 67 | void removeJupyterd(const string kernelPath) { 68 | import std.file : rmdir, copy, isFile; 69 | 70 | const ddir = kernelPath ~ "d/"; 71 | const k = ddir ~ "kernel.json"; 72 | enforce(isFile(k), "kernel.json not found in " ~ ddir); 73 | 74 | auto g = execute(["rm", k]); 75 | rmdir(ddir); 76 | 77 | const l = jdir ~ "jupyterd"; 78 | enforce(isFile(l), "jupyterd not found in " ~ jdir); 79 | g = execute(["rm", l]); 80 | } 81 | 82 | int main(string[] args) { 83 | bool install; 84 | bool remove; 85 | bool path; 86 | bool build; 87 | 88 | auto helpInformation = getopt(args, 89 | "install|i", "install or update the D Jupyter Kernel", &install, 90 | "remove|r", "remove the D Jupyter Kernel", &remove, 91 | "build|b", "force jupyterd build", &remove, 92 | "path|p", "display the path where Jupyter Kernel are installed", 93 | &path 94 | ); 95 | if(helpInformation.helpWanted) { 96 | defaultGetoptPrinter("Installer for the D Jupyter Kernel.\n" 97 | ~ "This needs to be executed with root privileges.", 98 | helpInformation.options); 99 | return 1; 100 | } 101 | 102 | string ppath = getKernelPath(); 103 | if(path) { 104 | writefln!"python2 kernel path: '%s'"(ppath); 105 | } 106 | 107 | if(install) { 108 | installJupyterd(ppath, build); 109 | return 0; 110 | } else if(remove) { 111 | removeJupyterd(ppath); 112 | return 0; 113 | } 114 | 115 | return 0; 116 | } 117 | -------------------------------------------------------------------------------- /source/jupyterd/message.d: -------------------------------------------------------------------------------- 1 | module jupyterd.message; 2 | 3 | import asdf; 4 | import std.json; 5 | 6 | import std.string : representation; 7 | import std.digest.hmac; 8 | import std.digest.sha; 9 | 10 | import std.stdio : writeln; //Debugging 11 | import juypterd.kernel : Kernel; 12 | /// MessageHeader encodes header info for ZMQ messages. 13 | struct MessageHeader 14 | { 15 | @serializationKeys("msg_id") 16 | string msgID; 17 | 18 | @serializationKeys("username") 19 | string userName; 20 | 21 | @serializationKeys("session") 22 | string session; 23 | 24 | @serializationKeys("msg_type") 25 | string msgType; 26 | 27 | @serializationKeys("version") 28 | string protocolVersion = Kernel.protocolVersion; 29 | 30 | @serializationKeys("date") 31 | string timestamp; 32 | } 33 | 34 | struct Message 35 | { 36 | string[] identities; 37 | MessageHeader header; 38 | MessageHeader parentHeader; 39 | JSONValue metadata; 40 | JSONValue content; 41 | this(MessageHeader parent,string[] ids,string userName,string session,string protocolVersion) 42 | { 43 | import std.datetime.date; 44 | import std.datetime.systime; 45 | import std.uuid; 46 | identities = ids; 47 | parentHeader = parent; 48 | header.userName = userName; 49 | header.session = session; 50 | header.protocolVersion = protocolVersion; 51 | header.timestamp = (cast(DateTime)Clock.currTime()).toISOExtString(); 52 | header.msgID = randomUUID().toString; 53 | } 54 | this(WireMessage wire) 55 | { 56 | identities = wire.identities; 57 | header = parseHeader(wire.header); 58 | parentHeader = parseHeader(wire.parentHeader); 59 | metadata = wire.metadata.parseJSON; 60 | content = wire.content.parseJSON; 61 | } 62 | } 63 | MessageHeader parseHeader(string j) 64 | { 65 | MessageHeader mh; 66 | if (j == "{}") 67 | return mh; 68 | auto vv = parseJSON(j); 69 | auto v = vv.object; 70 | 71 | mh.msgID = v["msg_id"].str; 72 | mh.userName = v["username"].str; 73 | mh.session = v["session"].str; 74 | mh.msgType = v["msg_type"].str; 75 | mh.protocolVersion = v["version"].str; 76 | mh.timestamp = v["date"].str; 77 | return mh; 78 | 79 | } 80 | Message message(WireMessage wm) 81 | { 82 | return Message(wm); 83 | } 84 | 85 | struct WireMessage 86 | { 87 | private import std.digest.sha; 88 | string[] identities; 89 | enum delimeter = ""; 90 | string sig; //HMAC signature 91 | string header; 92 | string parentHeader; 93 | string metadata; 94 | string content; 95 | string[] rawBuffers; 96 | 97 | this(string[] msgs) 98 | { 99 | int i = 0; 100 | 101 | while(msgs[i] != delimeter) i++; // find delimeter 102 | int j = 0; 103 | while (msgs[j].length == 0) j++; 104 | identities = msgs[j .. i]; 105 | 106 | 107 | i++; // Skip delimeter 108 | 109 | sig = msgs[i++]; 110 | header = msgs[i++]; 111 | parentHeader = msgs[i++]; 112 | metadata = msgs[i++]; 113 | content = msgs[i++]; 114 | rawBuffers = msgs[i .. $]; 115 | } 116 | 117 | this(Message m, string key) 118 | { 119 | import std.digest : hexDigest; 120 | identities = m.identities; 121 | 122 | 123 | header = m.header.serializeToJson; 124 | 125 | if (m.parentHeader.msgID is null) 126 | parentHeader = "{}"; 127 | else 128 | parentHeader = m.parentHeader.serializeToJson; 129 | 130 | if(m.metadata == JSONValue(null)) 131 | metadata = "{}"; 132 | else 133 | metadata = m.metadata.toString(JSONOptions.doNotEscapeSlashes); 134 | 135 | content = m.content.toString(JSONOptions.doNotEscapeSlashes); 136 | sig = signature(key); 137 | 138 | } 139 | 140 | string signature(string key) 141 | { 142 | import std.meta : AliasSeq; 143 | auto mac = hmac!SHA256(key.representation); 144 | foreach(w;AliasSeq!(header,parentHeader,metadata,content)) 145 | mac.put(w.representation); 146 | ubyte[32] us = mac.finish; 147 | import std.array : appender; 148 | import std.conv : toChars; 149 | auto cs = appender!string; 150 | cs.reserve(64); 151 | 152 | foreach(u;us[]) 153 | { 154 | if (u <= 0xf) 155 | cs.put('0'); 156 | cs.put(toChars!16(cast(uint)u)); 157 | } 158 | return cs.data.idup; 159 | } 160 | } 161 | 162 | WireMessage wireMessage(string[] msgs) 163 | { 164 | return WireMessage(msgs); 165 | } 166 | 167 | WireMessage wireMessage(Message m, string key) 168 | { 169 | return WireMessage(m,key); 170 | } 171 | 172 | -------------------------------------------------------------------------------- /source/jupyterd/kernel.d: -------------------------------------------------------------------------------- 1 | module juypterd.kernel; 2 | 3 | import jupyterd.message; 4 | import jupyterd.conn; 5 | import juypterd.interpreter; 6 | import std.json; 7 | import std.uuid; 8 | import std.concurrency; 9 | import asdf; 10 | import zmqd : Socket, SocketType, Frame, poll, PollItem, PollFlags; 11 | import core.time; 12 | import std.stdio : writeln; 13 | import drepl.interpreter : InterpreterResult; 14 | 15 | //TODOs: add kernel UUID to ZMQ identity for ioPub 16 | 17 | //debug = traffic; 18 | //debug = connect; 19 | void startHeartBeat(shared(Socket*) shbs, shared(bool*) run) 20 | { 21 | import core.thread; 22 | import core.atomic; 23 | 24 | auto hbs = cast(Socket*)shbs; 25 | ubyte[1024] data; 26 | while(atomicLoad(run)) 27 | { 28 | hbs.receive(data[]); 29 | hbs.send(data[]); 30 | data[] = 0; 31 | Thread.getThis().sleep(500.msecs); 32 | } 33 | } 34 | 35 | struct Channel 36 | { 37 | Socket socket; 38 | ushort port; 39 | string name; 40 | this(string n,SocketType t, ushort p) 41 | { 42 | name = n; 43 | socket = Socket(t); 44 | port = p; 45 | } 46 | void bind(const ref ConnectionInfo ci) 47 | { 48 | string s = ci.connectionString(port); 49 | debug(connect) writeln("Binding to: ",s); 50 | socket.bind(s); 51 | } 52 | Message getMessage(string key) 53 | { 54 | string[] frames; 55 | frames.length = 6; 56 | do { 57 | auto f = Frame(); 58 | socket.receive(f); 59 | frames ~= cast(string)f.data.idup; 60 | } while (socket.more); 61 | debug(traffic) 62 | { 63 | writeln("Recieved on ",name); 64 | foreach(f;frames) 65 | writeln("\t\"",f,"\""); 66 | } 67 | auto wm = frames.wireMessage; 68 | auto sig = wm.signature(key); 69 | writeln("\tSignature match? ", sig ,"\n\t ", wm.sig); 70 | return wm.message(); 71 | } 72 | void send(string ss, bool more) 73 | { 74 | debug(traffic) writeln("\t\"", ss,"\""); 75 | socket.send(ss,more); 76 | } 77 | void send(ref WireMessage wm) 78 | { 79 | debug(traffic) writeln("Sending on ",name); 80 | if (wm.identities) 81 | foreach(i;wm.identities) 82 | send(i,/*more=*/true); 83 | 84 | string blankIfNull(string s) 85 | { 86 | return s ? s : "{}"; 87 | } 88 | send(WireMessage.delimeter, true); 89 | send(wm.sig, true); 90 | send(wm.header,true); 91 | send(blankIfNull(wm.parentHeader),true); 92 | send(blankIfNull(wm.metadata) , true); 93 | send(wm.content,false); 94 | 95 | /+ 96 | send(s,wm.content, wm.rawBuffers !is null); 97 | if (wm.rawBuffers !is null) 98 | { 99 | foreach(b;wm.rawBuffers) 100 | send(b, true); 101 | } 102 | send("",false); 103 | +/ 104 | debug(traffic) writeln("sent."); 105 | } 106 | } 107 | 108 | struct Kernel 109 | { 110 | enum KernelVersion = "0.1"; 111 | enum protocolVersion = "5.3"; 112 | enum Status 113 | { 114 | ok, 115 | error, 116 | idle, 117 | busy 118 | } 119 | 120 | Channel shell, control, stdin, ioPub, hb; 121 | MessageHeader lastHeader; 122 | string userName, session, key; 123 | bool infoSet; 124 | // For keping track of In[n] and Out[n] 125 | int execCount = 1; 126 | Tid heartBeat; 127 | 128 | Interpreter interp; 129 | 130 | bool running; 131 | 132 | this(Interpreter interpreter, string connectionFile) 133 | { 134 | interp = interpreter; 135 | session = randomUUID.toString; 136 | 137 | import std.file : readText; 138 | auto ci = connectionFile.readText.deserialize!ConnectionInfo; 139 | 140 | key = ci.key.dup; 141 | enum shellType = SocketType.router; 142 | shell = Channel("shell" ,shellType, ci.shellPort); 143 | control = Channel("control",shellType, ci.controlPort); 144 | stdin = Channel("stdin" ,SocketType.router, ci.stdinPort); 145 | ioPub = Channel("ioPub" ,SocketType.pub, ci.ioPubPort); 146 | hb = Channel("hb" ,SocketType.rep, ci.hbPort); 147 | 148 | debug(connect) writeln("Commencing binding..."); 149 | shell.bind(ci); 150 | control.bind(ci); 151 | stdin.bind(ci); 152 | ioPub.bind(ci); 153 | hb.bind(ci); 154 | debug(connect) writeln("... done."); 155 | running = true; 156 | heartBeat = spawn(&startHeartBeat, cast(shared(Socket*))(&hb.socket), cast(shared(bool*))&running); 157 | } 158 | 159 | void mainloop() 160 | { 161 | while(running) 162 | { 163 | PollItem[] items = [ 164 | PollItem(shell.socket, PollFlags.pollIn), 165 | PollItem(control.socket, PollFlags.pollIn), 166 | ]; 167 | 168 | const n = poll(items, 100.msecs); 169 | if (n) 170 | { 171 | if (items[0].returnedEvents == PollFlags.pollIn) 172 | handleShellMessage(shell); 173 | 174 | // control is identical to shell but usually recieves shutdown & abort signals 175 | if (items[1].returnedEvents == PollFlags.pollIn) 176 | handleShellMessage(control); 177 | } 178 | 179 | //stdin (the channel) is used for requesting raw input from client. 180 | //not implemented yet 181 | 182 | //ioPub is written to by the handling of the other sockets. 183 | 184 | //heartbeat is handled on another thread. 185 | } 186 | } 187 | 188 | void handleShellMessage(ref Channel c) 189 | { 190 | auto m = c.getMessage(key); 191 | if (!infoSet) 192 | { 193 | userName = m.header.userName; 194 | session = m.header.session; 195 | infoSet = true; 196 | } 197 | lastHeader = m.header; 198 | publishStatus(Status.busy); 199 | auto msg = Message(m.header,m.identities,userName,session,protocolVersion); 200 | 201 | switch(m.header.msgType) 202 | { 203 | case "execute_request": 204 | { 205 | if(executeRequest(msg,m.content)) 206 | { 207 | publishStatus(Status.idle); 208 | return; 209 | } 210 | break; 211 | } 212 | case "inspect_request": 213 | { 214 | //TODO request info from interpreter 215 | return; 216 | } 217 | case "complete_request": 218 | { 219 | //TODO request autocomplete from interpreter 220 | return; 221 | } 222 | case "history_request": 223 | { 224 | //TODO request history from interpreter 225 | return; 226 | } 227 | case "is_complete_request": 228 | { 229 | //TODO request autocomplete 230 | return; 231 | } 232 | 233 | //deprecated in jupyter 5.1 234 | case "connect_request": 235 | { 236 | connectRequest(msg); 237 | break; 238 | } 239 | case "comm_info_request": 240 | { 241 | //TODO kernel comms 242 | return; 243 | } 244 | case "kernel_info_request": 245 | { 246 | kernelInfoRequest(msg); 247 | break; 248 | } 249 | case "shutdown_request": 250 | { 251 | shutdownRequest(msg, m.content["restart"] == JSONValue(true)); 252 | break; 253 | } 254 | default: return; 255 | } 256 | auto wm = msg.wireMessage(key); 257 | c.send(wm); 258 | publishStatus(Status.idle); 259 | 260 | } 261 | 262 | bool executeRequest(ref Message msg, ref JSONValue content) 263 | { 264 | msg.header.msgType = "execute_reply"; 265 | const bool silent = content["silent"] == JSONValue(true); 266 | 267 | const history = (content["store_history"] == JSONValue(true)) && silent; 268 | string code = content["code"].str; 269 | if (!silent) 270 | { 271 | publishInputMsg(code); 272 | } 273 | 274 | auto res = interp.interpret(code); 275 | 276 | const succeded = res.state == InterpreterResult.State.success; 277 | 278 | if (!succeded) 279 | { 280 | if (res.state == InterpreterResult.State.incomplete) 281 | { 282 | msg.content["ename"] = "Incomplete request"; 283 | msg.content["evalue"] = "Incomplete request"; 284 | msg.content["traceback"] = [""]; 285 | } 286 | else // error 287 | { 288 | msg.content["ename"] = "Error"; 289 | //TODO: create traceback 290 | msg.content["evalue"] = res.stdout; 291 | msg.content["traceback"] = [""]; 292 | 293 | } 294 | msg.content["status"] = "error"; 295 | if (res.stderr.length) 296 | publishStreamText("stderr",res.stderr); 297 | } 298 | else 299 | { 300 | msg.content["status"] = "ok"; 301 | //if (res.stdout.length) 302 | // publishStreamText("stdout",res.stdout); 303 | } 304 | 305 | msg.content["execution_count"] = execCount; 306 | publishExecResults(res.stdout); 307 | if (history && succeded) execCount++; 308 | return silent; 309 | } 310 | 311 | 312 | void shutdownRequest(ref Message msg, bool restart) 313 | { 314 | //TODO: Handle restart 315 | msg.header.msgType = "shutdown_reply"; 316 | running = false; 317 | msg.content["restart"] = restart; 318 | } 319 | 320 | void kernelInfoRequest(ref Message msg) 321 | { 322 | msg.header.msgType = "kernel_info_reply"; 323 | msg.content["protocol_version"] = "5.3.0"; 324 | msg.content["implementation"] = "JupyterD"; 325 | msg.content["implementation_version"] = KernelVersion; 326 | auto li = interp.languageInfo(); 327 | msg.content["language_info"] = ["name" : li.name, 328 | "version" : li.languageVersion, 329 | "mimetype" : li.mimeType, 330 | "file_extension" :li.fileExtension]; 331 | 332 | } 333 | 334 | void connectRequest(ref Message msg) 335 | { 336 | msg.header.msgType = "connect_reply"; 337 | msg.content["shell_port"] = shell.port; 338 | msg.content["iopub_port"] = ioPub.port; 339 | msg.content["stdin_port"] = stdin.port; 340 | msg.content["hb_port"] = hb.port; 341 | msg.content["control_port"] = control.port; 342 | } 343 | 344 | Message newIOPubMsg(string hdrName) 345 | { 346 | auto m = Message(lastHeader,["kernel."~session~"." ~ hdrName],userName,session,protocolVersion); 347 | m.header.msgType = hdrName; 348 | return m; 349 | } 350 | 351 | void sendIOPubMsg(ref Message msg) 352 | { 353 | auto wm = msg.wireMessage(key); 354 | ioPub.send(wm); 355 | } 356 | 357 | void publishExecResults(string stdout) 358 | { 359 | auto msg = newIOPubMsg("execute_result"); 360 | msg.content["execution_count"] = execCount; 361 | msg.content["data"] = ["text/plain" : stdout]; 362 | string[string] dummy; 363 | msg.content["metadata"] = dummy; 364 | sendIOPubMsg(msg); 365 | } 366 | 367 | void publishStreamText(string stream, string text) 368 | { 369 | auto msg = newIOPubMsg("stream"); 370 | msg.content["name"] = stream; 371 | msg.content["text"] = text; 372 | sendIOPubMsg(msg); 373 | } 374 | 375 | void publishStatus(Status status) 376 | { 377 | auto msg = newIOPubMsg("status"); 378 | import std.conv : to; 379 | msg.content["execution_state"] = status.to!string; 380 | 381 | sendIOPubMsg(msg); 382 | } 383 | 384 | void publishInputMsg(string code) 385 | { 386 | auto msg = newIOPubMsg("execute_input"); 387 | msg.content["code"] = code; 388 | msg.content["execution_count"] = execCount; 389 | sendIOPubMsg(msg); 390 | } 391 | } 392 | 393 | //MASSIVE HACK: allow compiling on OSX for testing echo engine 394 | version(OSX) 395 | extern(C) void* rt_loadLibrary(const char* name) 396 | { 397 | return null; 398 | } 399 | --------------------------------------------------------------------------------