├── LICENSE ├── README.md └── ipython_kernel.lua /LICENSE: -------------------------------------------------------------------------------- 1 | lua_ipython_kernel 2 | 3 | Copyright (c) 2013 Evan Wies. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lua IPython Kernel 2 | 3 | This is a kernel to support Lua with [IPython](http://ipython.org). It is pure Lua and should work with both Lua and LuaJIT. 4 | 5 | 6 | ## Requirements 7 | 8 | The following Lua libraries are required: 9 | 10 | * [zeromq](http://zeromq.org/bindings:lua) 11 | * [dkjson](http://dkolf.de/src/dkjson-lua.fsl/home) 12 | * [uuid](https://github.com/Tieske/uuid) 13 | 14 | Here's how to install via [LuaRocks](http://luarocks.org/): 15 | 16 | ``` 17 | # You need zeromq... on OSX I use Homebrew: 18 | # brew install zeromq 19 | # On Ubuntu: 20 | # sudo apt-get install libzmq-dev 21 | 22 | # The LuaRocks 23 | sudo luarocks install https://raw.github.com/Neopallium/lua-zmq/master/rockspecs/lua-zmq-scm-1.rockspec 24 | sudo luarocks install dkjson 25 | sudo luarocks install uuid 26 | ``` 27 | 28 | Of course you also need to [install IPython](http://ipython.org/install.html)... 29 | 30 | 31 | ## Installation 32 | 33 | The installation process is janky right now. 34 | 35 | * Install the Requirements above 36 | 37 | * Create a profile with IPython 38 | 39 | ``` 40 | ipython profile create lua 41 | ``` 42 | 43 | * Modify the profile's `ipython_config.py` to use lua_ipython_kernel. This will be at either 44 | `~/.config/ipython/lua/ipython_config.py` or `~/.ipython/lua/ipython_config.py`: 45 | 46 | ``` 47 | # Configuration file for ipython. 48 | 49 | c = get_config() 50 | 51 | c.KernelManager.kernel_cmd = [ 52 | "luajit", # select your Lua interpreter here 53 | "ipython_kernel.lua", # probably need full path 54 | "{connection_file}" 55 | ] 56 | 57 | # Disable authentication. 58 | c.Session.key = b'' 59 | c.Session.keyfile = b'' 60 | ``` 61 | 62 | * Invoke IPython with this Lua kernel: 63 | 64 | ``` 65 | ipython console --profile lua 66 | # or 67 | ipython notebook --profile lua 68 | ``` 69 | 70 | ## TODO 71 | 72 | * Get the execute side of things working 73 | * Go through all the TODOs in the source file 74 | * Make a luarock spec 75 | * Make an installer 76 | * HMAC 77 | 78 | 79 | ## Acknowledgements 80 | 81 | Thanks to Andrew Gibiansky for his [IHaskell article](http://andrew.gibiansky.com/blog/ipython/ipython-kernels/) that inspired this. 82 | 83 | Thanks to the makers of the dependencies of this library, who made this pretty easy to create: [Robert Jakabosky](https://github.com/Neopallium), [David Kolf](http://dkolf.de/src/dkjson-lua.fsl/home), and [Thijs Schreijer](https://github.com/Tieske). 84 | 85 | And of course thanks to the [IPython folks ](http://ipython.org/citing.html). 86 | 87 | 88 | ## LICENSE 89 | 90 | **lua_ipython_kernel** is distributed under the [MIT License](http://opensource.org/licenses/mit-license.php). 91 | 92 | > lua_ipython_kernel 93 | > 94 | > Copyright (c) 2013 Evan Wies. All rights reserved. 95 | > 96 | > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 97 | > 98 | > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 99 | > 100 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 101 | -------------------------------------------------------------------------------- /ipython_kernel.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | lua_ipython_kernel 3 | 4 | Copyright (c) 2013 Evan Wies. All rights reserved. 5 | 6 | Released under the MIT License, see the LICENSE file. 7 | 8 | https://github.com/neomantra/lua_ipython_kernel 9 | 10 | usage: lua ipython_kernel.lua `connection_file` 11 | ]] 12 | 13 | 14 | if #arg ~= 1 then 15 | io.stderr:write('usage: ipython_kernel.lua `connection_filename`\n') 16 | os.exit(-1) 17 | end 18 | 19 | local json = require 'dkjson' 20 | 21 | -- our kernel's state 22 | local kernel = {} 23 | 24 | -- load connection object info from JSON file 25 | do 26 | local connection_file = io.open(arg[1]) 27 | if not connection_file then 28 | io.stderr:write('couldn not open connection file "', arg[1], '")": ', err, '\n') 29 | os.exit(-1) 30 | end 31 | 32 | local connection_json, err = connection_file:read('*a') 33 | if not connection_json then 34 | io.stderr:write('couldn not read connection file "', arg[1], '")": ', err, '\n') 35 | os.exit(-1) 36 | end 37 | 38 | kernel.connection_obj = json.decode(connection_json) 39 | if not kernel.connection_obj then 40 | io.stderr:write('connection file is missing connection object\n') 41 | os.exit(-1) 42 | end 43 | connection_file:close() 44 | end 45 | 46 | 47 | local zmq = require 'zmq' 48 | local zmq_poller = require 'zmq/poller' 49 | local z_NOBLOCK, z_POLLIN = zmq.NOBLOCK, zmq.POLL_IN 50 | local z_RCVMORE, z_SNDMORE = zmq.RCVMORE, zmq.SNDMORE 51 | 52 | local uuid = require 'uuid' 53 | -- TODO: randomseed or luasocket or something else 54 | 55 | local username = os.getenv('USER') 56 | 57 | 58 | 59 | ------------------------------------------------------------------------------- 60 | -- IPython Message ("ipmsg") functions 61 | 62 | local function ipmsg_to_table(parts) 63 | local ipmsg = { ids = {}, blobs = {} } 64 | 65 | local i = 1 66 | while i <= #parts and parts[i] ~= '' do 67 | ipmsg.ids[#ipmsg.ids + 1] = parts[i] 68 | i = i + 1 69 | end 70 | i = i + 1 71 | ipmsg.hmac = parts[i] ; i = i + 1 72 | ipmsg.header = parts[i] ; i = i + 1 73 | ipmsg.parent_header = parts[i] ; i = i + 1 74 | ipmsg.metadata = parts[i] ; i = i + 1 75 | ipmsg.content = parts[i] ; i = i + 1 76 | 77 | while i <= #parts do 78 | ipmsg.blobs[#ipmsg.blobs + 1] = parts[i] 79 | i = i + 1 80 | end 81 | 82 | return ipmsg 83 | end 84 | 85 | 86 | local function ipmsg_header(session, msg_type) 87 | return json.encode({ 88 | msg_id = uuid.new(), -- TODO: randomness warning 89 | username = username, 90 | date = os.date('%Y-%m-%dT%h:%M:%s.000000'), -- TODO milliseconds 91 | session = session, 92 | msg_type = msg_type, 93 | }) 94 | end 95 | 96 | 97 | local function ipmsg_recv(sock) 98 | -- TODO: error handling 99 | local parts, err = {} 100 | parts[1], err = sock:recv() 101 | while sock:getopt(z_RCVMORE) == 1 do 102 | parts[#parts + 1], err = sock:recv() 103 | end 104 | return parts, err 105 | end 106 | 107 | 108 | local function ipmsg_send(sock, ids, hmac, hdr, p_hdr, meta, content, blobs) 109 | if type(ids) == 'table' then 110 | for _, v in ipairs(ids) do 111 | sock:send(v, z_SNDMORE) 112 | end 113 | else 114 | sock:send(ids, z_SNDMORE) 115 | end 116 | sock:send('', z_SNDMORE) 117 | sock:send(hmac, z_SNDMORE) 118 | sock:send(hdr, z_SNDMORE) 119 | sock:send(p_hdr, z_SNDMORE) 120 | sock:send(meta, z_SNDMORE) 121 | if blobs then 122 | sock:send(content, z_SNDMORE) 123 | if type(blobs) == 'table' then 124 | for i, v in ipairs(blobs) do 125 | if i == #blobs then 126 | sock:send(v) 127 | else 128 | sock:send(v, z_SNDMORE) 129 | end 130 | end 131 | else 132 | sock:send(blobs) 133 | end 134 | else 135 | sock:send(content) 136 | end 137 | end 138 | 139 | 140 | 141 | ------------------------------------------------------------------------------- 142 | -- ZMQ Read Handlers 143 | 144 | local function on_hb_read( sock ) 145 | -- read the data and send a pong 146 | local data, err = sock:recv(zmq.NOBLOCK) 147 | if not data then 148 | assert(err == 'timeout', 'Bad error on zmq socket.') 149 | -- TODO return sock_blocked(on_sock_recv, z_POLLIN) 150 | assert(false, "bad hb_read_data") 151 | end 152 | sock:send('pong') 153 | end 154 | 155 | local function on_control_read( sock ) 156 | -- read the data and send a pong 157 | local data, err = sock:recv(zmq.NOBLOCK) 158 | if not data then 159 | assert(err == 'timeout', 'Bad error on zmq socket.') 160 | -- TODO return sock_blocked(on_sock_recv, z_POLLIN) 161 | assert(false, "bad hb_read_data") 162 | end 163 | -- print('control', data) 164 | end 165 | 166 | local function on_stdin_read( sock ) 167 | -- read the data and send a pong 168 | local data, err = sock:recv(zmq.NOBLOCK) 169 | if not data then 170 | assert(err == 'timeout', 'Bad error on zmq socket.') 171 | -- TODO return sock_blocked(on_sock_recv, z_POLLIN) 172 | assert(false, "bad hb_read_data") 173 | end 174 | -- print('stdin', data) 175 | end 176 | 177 | 178 | 179 | local function on_shell_read( sock ) 180 | 181 | local ipmsg, err = ipmsg_recv(sock) 182 | local msg = ipmsg_to_table(ipmsg) 183 | 184 | for k, v in pairs(msg) do print(k,v) end 185 | 186 | local header_obj = json.decode(msg.header) 187 | if header_obj.msg_type == 'kernel_info_request' then 188 | local header = ipmsg_header( header_obj.session, 'kernel_info_reply' ) 189 | local content = json.encode({ 190 | protocol_version = {4, 0}, 191 | language_version = {5, 1}, 192 | language = 'lua', 193 | }) 194 | 195 | ipmsg_send(sock, header_obj.session, '', header, msg.header, '{}', content) 196 | 197 | elseif header_obj.msg_type == 'execute_request' then 198 | 199 | local header = ipmsg_header( header_obj.session, 'execute_reply' ) 200 | kernel.execution_count = kernel.execution_count + 1 201 | local content = json.encode({ 202 | status = 'ok', 203 | execution_count = kernel.execution_count, 204 | }) 205 | 206 | ipmsg_send(sock, header_obj.session, '', header, msg.header, '{}', content) 207 | 208 | end 209 | 210 | end 211 | 212 | local function on_iopub_read( sock ) 213 | -- read the data and send a pong 214 | local data, err = sock:recv(zmq.NOBLOCK) 215 | if not data then 216 | assert(err == 'timeout', 'Bad error on zmq socket.') 217 | -- TODO return sock_blocked(on_sock_recv, z_POLLIN) 218 | assert(false, "bad hb_read_data") 219 | end 220 | -- print('iopub', data) 221 | 222 | end 223 | 224 | 225 | ------------------------------------------------------------------------------- 226 | -- SETUP 227 | 228 | local kernel_sockets = { 229 | { name = 'heartbeat_sock', sock_type = zmq.REP, port = 'hb', handler = on_hb_read }, 230 | { name = 'control_sock', sock_type = zmq.ROUTER, port = 'control', handler = on_control_read }, 231 | { name = 'stdin_sock', sock_type = zmq.ROUTER, port = 'stdin', handler = on_stdin_read }, 232 | { name = 'shell_sock', sock_type = zmq.ROUTER, port = 'shell', handler = on_shell_read }, 233 | { name = 'iopub_sock', sock_type = zmq.PUB, port = 'iopub', handler = on_iopub_read }, 234 | } 235 | 236 | local z_ctx = zmq.init() 237 | local z_poller = zmq_poller(#kernel_sockets) 238 | for _, v in ipairs(kernel_sockets) do 239 | -- TODO: error handling in here 240 | local sock = z_ctx:socket(v.sock_type) 241 | 242 | local conn_obj = kernel.connection_obj 243 | local addr = string.format('%s://%s:%s', 244 | conn_obj.transport, 245 | conn_obj.ip, 246 | conn_obj[v.port..'_port']) 247 | 248 | -- io.stderr:write(string.format('binding %s to %s\n', v.name, addr)) 249 | sock:bind(addr) 250 | 251 | z_poller:add(sock, zmq.POLLIN, v.handler) 252 | 253 | kernel[v.name] = sock 254 | end 255 | 256 | kernel.execution_count = 0 257 | 258 | 259 | ------------------------------------------------------------------------------- 260 | -- POLL then SHUTDOWN 261 | 262 | --print("Starting poll") 263 | z_poller:start() 264 | 265 | for _, v in ipairs(kernel_sockets) do 266 | kernel[v.name]:close() 267 | end 268 | z_ctx:term() 269 | --------------------------------------------------------------------------------