iruby 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -64
- data/Ruby Notebook.ipynb +6 -69
- data/{test → attic/test}/helper.rb +0 -0
- data/{test → attic/test}/html_test.rb +0 -0
- data/{test → attic/test}/output_maps_test.rb +0 -0
- data/lib/iruby.rb +1 -2
- data/lib/iruby/command.rb +2 -33
- data/lib/iruby/kernel.rb +87 -146
- data/lib/iruby/ostream.rb +42 -0
- data/lib/iruby/session.rb +54 -268
- data/lib/iruby/version.rb +1 -1
- data/static/base/images/favicon.ico +0 -0
- metadata +7 -11
- data/lib/iruby/display_hook.rb +0 -29
- data/lib/iruby/out_stream.rb +0 -93
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b8da3f0c37d62309c5a113b463ab8567b1c49b9
|
4
|
+
data.tar.gz: 36649a74bcb7dbcd7c91628efc8fd37c8a6bf211
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d1d55e0f01d3ddc87cde6a3d6a610265c010ac8c4b0cd22b18d326c85c783e462e23258e32ab6f3afaebbf83c19b0737c75793bcc2968e7b70d96377c243aa2
|
7
|
+
data.tar.gz: 98f46f4e30e7c8f745c783ffeaa89e16c6a79f71b18abc097a6b45c8ad50a48fe94927f11b5f52d545884201f45df59e831c98eaf1587cb6d3c21352a14ed7f7
|
data/README.md
CHANGED
@@ -2,69 +2,6 @@
|
|
2
2
|
|
3
3
|
This is a Ruby kernel for IPython.
|
4
4
|
|
5
|
-
It adds a special `iruby_profile` command for staging some customization
|
6
|
-
that enables the Ruby kernel by default, and sets syntax-highlighting in the notebook
|
7
|
-
to Ruby mode.
|
8
|
-
|
9
5
|
### Usage
|
10
6
|
|
11
|
-
|
12
|
-
use IPython as usual:
|
13
|
-
|
14
|
-
```bash
|
15
|
-
git clone git://github.com/minrk/iruby
|
16
|
-
cd iruby
|
17
|
-
# build and install IRuby
|
18
|
-
gem build iruby.gemspec
|
19
|
-
$ gem install iruby-*.gem
|
20
|
-
# Create an IPython profile with default config
|
21
|
-
$ iruby_profile --create
|
22
|
-
$ ipython notebook --profile=ruby
|
23
|
-
```
|
24
|
-
|
25
|
-
|
26
|
-
## Background
|
27
|
-
|
28
|
-
### Building an in-browser REPL for Ruby (IRuby)
|
29
|
-
|
30
|
-
Hey, I'm Josh Adams. I'm a partner and CTO at isotope|eleven. We alo host
|
31
|
-
Birmingham, AL's Open Source Software meetup - BOSS.
|
32
|
-
|
33
|
-
At one of these sessions in early 2012, Tom Brander did a presentation and used
|
34
|
-
IPython in his browser to manage it (there was much code and it was executed
|
35
|
-
live). This was the first time I'd seen IPython in the browser where it
|
36
|
-
actually worked like it was supposed to, and I was extremely impressed.
|
37
|
-
|
38
|
-
If you've not seen IPython, it looks like this <* Insert Screenshot Here *> in
|
39
|
-
its web-browser mode. It also manages a lot of console-basd REPLs.
|
40
|
-
|
41
|
-
Anyway, it has notebooks that you can save out to execute later, and you can
|
42
|
-
pass them around as little code snippets for other people to check out. It's
|
43
|
-
very impressive.
|
44
|
-
|
45
|
-
But I'm primarily a Rubyist, and I'm happy that way :) I couldn't sit by while
|
46
|
-
Python had this awesome tool that we lacked. I looked around for a bit, and
|
47
|
-
there was nothing like IPython in our ecosystem. There were, however, quite a
|
48
|
-
few people asking about it. So I figured I'd do something about it.
|
49
|
-
|
50
|
-
#### The Architecture
|
51
|
-
|
52
|
-
So the IPython guys did a great job explaining their core architecture, both in
|
53
|
-
words and in pared-down code, in a blog post they wrote concerning it. In
|
54
|
-
general, it works like this <* Diagram *>
|
55
|
-
|
56
|
-
There's a kernel that runs in the background and gets connected to by a
|
57
|
-
frontend. They communicate using zeromq, and they send json formatted messages
|
58
|
-
back and forth. These messages are in a very well defined structure. Anyway,
|
59
|
-
this way the frontend of the repl is disconnected from the environment that's
|
60
|
-
running it.
|
61
|
-
|
62
|
-
So the code repository they linked to in their blog post included the kernel and
|
63
|
-
the frontend as small-ish python files - around 300 and 200 lines respectively.
|
64
|
-
We had a hack weekend at isotope|eleven where myself and Robby Clements got
|
65
|
-
together and (when we weren't playing Counterstrike1.6) did the closest thing to
|
66
|
-
a straight port that we could swing. Within about 2 hours of work, we had a
|
67
|
-
working proof of concept that was primarily a 1 to 1 port.
|
68
|
-
|
69
|
-
The next move was to build the web frontend. This just consists of a websocket
|
70
|
-
server and a fairly basic frontend webpage.
|
7
|
+
Install the rubygem using `gem install iruby` and then run `iruby notebook`.
|
data/Ruby Notebook.ipynb
CHANGED
@@ -1,47 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"metadata": {
|
3
|
-
"name": ""
|
3
|
+
"name": "Ruby Notebook"
|
4
4
|
},
|
5
5
|
"nbformat": 3,
|
6
6
|
"nbformat_minor": 0,
|
7
7
|
"worksheets": [
|
8
8
|
{
|
9
9
|
"cells": [
|
10
|
-
{
|
11
|
-
"cell_type": "code",
|
12
|
-
"collapsed": false,
|
13
|
-
"input": [
|
14
|
-
"puts \"hi\"\n",
|
15
|
-
"raise \"zofe\""
|
16
|
-
],
|
17
|
-
"language": "python",
|
18
|
-
"metadata": {},
|
19
|
-
"outputs": [
|
20
|
-
{
|
21
|
-
"output_type": "stream",
|
22
|
-
"stream": "stdout",
|
23
|
-
"text": [
|
24
|
-
"hi\n"
|
25
|
-
]
|
26
|
-
},
|
27
|
-
{
|
28
|
-
"ename": "RuntimeError",
|
29
|
-
"evalue": "zofe",
|
30
|
-
"output_type": "pyerr",
|
31
|
-
"traceback": [
|
32
|
-
"\u001b[31mRuntimeError\u001b[0m: zofe",
|
33
|
-
"\u001b[37m<main>:1:in `<main>'\u001b[0m",
|
34
|
-
"\u001b[37m/home/minad/code/git/iruby/lib/iruby/kernel.rb:116:in `eval'\u001b[0m",
|
35
|
-
"\u001b[37m/home/minad/code/git/iruby/lib/iruby/kernel.rb:116:in `execute_request'\u001b[0m",
|
36
|
-
"\u001b[37m/home/minad/code/git/iruby/lib/iruby/kernel.rb:176:in `start'\u001b[0m",
|
37
|
-
"\u001b[37m/home/minad/code/git/iruby/lib/iruby/command.rb:60:in `run_kernel'\u001b[0m",
|
38
|
-
"\u001b[37m/home/minad/code/git/iruby/lib/iruby/command.rb:15:in `run'\u001b[0m",
|
39
|
-
"\u001b[37m/home/minad/code/git/iruby/bin/iruby:6:in `<main>'\u001b[0m"
|
40
|
-
]
|
41
|
-
}
|
42
|
-
],
|
43
|
-
"prompt_number": 3
|
44
|
-
},
|
45
10
|
{
|
46
11
|
"cell_type": "markdown",
|
47
12
|
"metadata": {},
|
@@ -65,22 +30,11 @@
|
|
65
30
|
" end\n",
|
66
31
|
"end\n",
|
67
32
|
"\n",
|
68
|
-
"Neat.new.eh
|
69
|
-
"\n",
|
70
|
-
"puts \"hi\""
|
33
|
+
"Neat.new.eh?"
|
71
34
|
],
|
72
|
-
"language": "
|
35
|
+
"language": "ruby",
|
73
36
|
"metadata": {},
|
74
|
-
"outputs": [
|
75
|
-
{
|
76
|
-
"output_type": "stream",
|
77
|
-
"stream": "stdout",
|
78
|
-
"text": [
|
79
|
-
"hi\n"
|
80
|
-
]
|
81
|
-
}
|
82
|
-
],
|
83
|
-
"prompt_number": 2
|
37
|
+
"outputs": []
|
84
38
|
},
|
85
39
|
{
|
86
40
|
"cell_type": "markdown",
|
@@ -106,26 +60,9 @@
|
|
106
60
|
"\n",
|
107
61
|
"ThatsPrettyCool.new.how_does_it_work?"
|
108
62
|
],
|
109
|
-
"language": "
|
63
|
+
"language": "ruby",
|
110
64
|
"metadata": {},
|
111
|
-
"outputs": [
|
112
|
-
{
|
113
|
-
"output_type": "stream",
|
114
|
-
"stream": "stdout",
|
115
|
-
"text": [
|
116
|
-
"I'm glad you asked!\n"
|
117
|
-
]
|
118
|
-
},
|
119
|
-
{
|
120
|
-
"metadata": {},
|
121
|
-
"output_type": "pyout",
|
122
|
-
"prompt_number": 4,
|
123
|
-
"text": [
|
124
|
-
"see_below"
|
125
|
-
]
|
126
|
-
}
|
127
|
-
],
|
128
|
-
"prompt_number": 4
|
65
|
+
"outputs": []
|
129
66
|
},
|
130
67
|
{
|
131
68
|
"cell_type": "markdown",
|
File without changes
|
File without changes
|
File without changes
|
data/lib/iruby.rb
CHANGED
data/lib/iruby/command.rb
CHANGED
@@ -25,39 +25,8 @@ module IRuby
|
|
25
25
|
Dir.chdir(working_dir) if working_dir
|
26
26
|
require boot_file if boot_file
|
27
27
|
require 'iruby'
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
puts 'Starting the kernel'
|
32
|
-
puts config
|
33
|
-
|
34
|
-
c = ZMQ::Context.new
|
35
|
-
|
36
|
-
connection = "#{config['transport']}://#{config['ip']}:%d"
|
37
|
-
|
38
|
-
session = IRuby::Session.new('kernel', config['key'], config['signature_scheme'])
|
39
|
-
|
40
|
-
reply_socket = c.socket(ZMQ::XREP)
|
41
|
-
reply_socket.bind(connection % config['shell_port'])
|
42
|
-
|
43
|
-
pub_socket = c.socket(ZMQ::PUB)
|
44
|
-
pub_socket.bind(connection % config['iopub_port'])
|
45
|
-
|
46
|
-
hb_thread = Thread.new do
|
47
|
-
hb_socket = c.socket(ZMQ::REP)
|
48
|
-
hb_socket.bind(connection % config['hb_port'])
|
49
|
-
ZMQ::Device.new(ZMQ::FORWARDER, hb_socket, hb_socket)
|
50
|
-
end
|
51
|
-
|
52
|
-
puts "Use Ctrl-\\ (NOT Ctrl-C!) to terminate."
|
53
|
-
|
54
|
-
$stdout = IRuby::OutStream.new(session, pub_socket, 'stdout')
|
55
|
-
#$stderr = OutStream.new(session, pub_socket, 'stderr')
|
56
|
-
|
57
|
-
kernel = IRuby::Kernel.new(session, reply_socket, pub_socket)
|
58
|
-
$displayhook = IRuby::DisplayHook.new(kernel, session, pub_socket)
|
59
|
-
|
60
|
-
kernel.start
|
28
|
+
$iruby_kernel = Kernel.new(config_file)
|
29
|
+
$iruby_kernel.run
|
61
30
|
end
|
62
31
|
|
63
32
|
def run_ipython
|
data/lib/iruby/kernel.rb
CHANGED
@@ -1,193 +1,134 @@
|
|
1
1
|
module IRuby
|
2
|
+
class Kernel
|
3
|
+
RED = "\e[31m"
|
4
|
+
WHITE = "\e[37m"
|
5
|
+
RESET = "\e[0m"
|
2
6
|
|
3
|
-
|
4
|
-
|
5
|
-
@response = response
|
6
|
-
@mime = mime
|
7
|
-
end
|
8
|
-
attr_reader :mime
|
9
|
-
def inspect
|
10
|
-
@response.inspect
|
11
|
-
end
|
12
|
-
def to_s
|
13
|
-
@response.to_s
|
14
|
-
end
|
15
|
-
end
|
7
|
+
def initialize(config_file)
|
8
|
+
config = MultiJson.load(File.read(config_file))
|
16
9
|
|
17
|
-
|
18
|
-
|
19
|
-
@output_mime = "text/html"
|
20
|
-
s
|
21
|
-
end
|
10
|
+
puts 'Starting the kernel'
|
11
|
+
puts config
|
22
12
|
|
23
|
-
|
24
|
-
@execution_count
|
25
|
-
end
|
13
|
+
c = ZMQ::Context.new
|
26
14
|
|
27
|
-
|
28
|
-
@
|
29
|
-
|
15
|
+
connection = "#{config['transport']}://#{config['ip']}:%d"
|
16
|
+
@reply_socket = c.socket(ZMQ::XREP)
|
17
|
+
@reply_socket.bind(connection % config['shell_port'])
|
18
|
+
|
19
|
+
@pub_socket = c.socket(ZMQ::PUB)
|
20
|
+
@pub_socket.bind(connection % config['iopub_port'])
|
21
|
+
|
22
|
+
@hb_thread = Thread.new do
|
23
|
+
hb_socket = c.socket(ZMQ::REP)
|
24
|
+
hb_socket.bind(connection % config['hb_port'])
|
25
|
+
ZMQ::Device.new(ZMQ::FORWARDER, hb_socket, hb_socket)
|
26
|
+
end
|
27
|
+
|
28
|
+
puts 'Use Ctrl-\\ (NOT Ctrl-C!) to terminate.'
|
29
|
+
|
30
|
+
@session = Session.new('kernel', config['key'], config['signature_scheme'])
|
31
|
+
|
32
|
+
$stdout = OStream.new(@session, @pub_socket, 'stdout')
|
33
|
+
$stderr = OStream.new(@session, @pub_socket, 'stderr')
|
30
34
|
|
31
|
-
def initialize session, reply_socket, pub_socket
|
32
|
-
@session = session
|
33
|
-
@reply_socket = reply_socket
|
34
|
-
@pub_socket = pub_socket
|
35
|
-
@history = []
|
36
35
|
@execution_count = 0
|
37
36
|
@completer = Completer.new
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
def run
|
40
|
+
send_status('starting')
|
41
|
+
while true
|
42
|
+
ident, msg = @session.recv(@reply_socket, 0)
|
43
|
+
type = msg[:header]['msg_type']
|
44
|
+
if type =~ /_request\Z/ && respond_to?(type)
|
45
|
+
send(type, ident, msg)
|
46
|
+
else
|
47
|
+
STDERR.puts "Unknown message type: #{msg[:header]['msg_type']} #{msg.inspect}"
|
48
|
+
end
|
43
49
|
end
|
44
50
|
end
|
45
51
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
#break
|
53
|
-
#else
|
54
|
-
#assert self.reply_socket.rcvmore(), "Unexpected missing message part."
|
55
|
-
#msg = self.reply_socket.recv_json()
|
56
|
-
#end
|
57
|
-
#end
|
58
|
-
msg_type = msg['header']['msg_type']
|
59
|
-
reply_type = msg_type.split('_')[0] + '_reply'
|
60
|
-
@session.send(@reply_socket, reply_type, {status: 'aborted'}, msg)
|
61
|
-
# reply_msg = @session.msg(reply_type, {status: 'aborted'}, msg)
|
62
|
-
# @reply_socket.send(ident,ZMQ::SNDMORE)
|
63
|
-
# @reply_socket.send(reply_msg.to_json)
|
64
|
-
# We need to wait a bit for requests to come in. This can probably
|
65
|
-
# be set shorter for true asynchronous clients.
|
66
|
-
sleep(0.1)
|
52
|
+
def display(obj, options={})
|
53
|
+
if obj
|
54
|
+
data = {}
|
55
|
+
data[obj.respond_to?(:mime) ? obj.mime : (options[:mime] || 'text/plain')] = obj.to_s
|
56
|
+
content = { data: data, metadata: {}, execution_count: @execution_count }
|
57
|
+
@session.send(@pub_socket, 'pyout', content)
|
67
58
|
end
|
59
|
+
nil
|
68
60
|
end
|
69
61
|
|
70
|
-
def kernel_info_request(ident,
|
71
|
-
|
62
|
+
def kernel_info_request(ident, msg)
|
63
|
+
content = {
|
72
64
|
protocol_version: [4, 0],
|
73
65
|
|
74
66
|
# Language version number (mandatory).
|
75
67
|
# It is Python version number (e.g., [2, 7, 3]) for the kernel
|
76
68
|
# included in IPython.
|
77
|
-
language_version: RUBY_VERSION.split('.').map
|
69
|
+
language_version: RUBY_VERSION.split('.').map(&:to_i),
|
78
70
|
|
79
71
|
# Programming language in which kernel is implemented (mandatory).
|
80
72
|
# Kernel included in IPython returns 'python'.
|
81
|
-
language:
|
73
|
+
language: 'ruby'
|
82
74
|
}
|
83
|
-
|
84
|
-
reply_content, parent, ident
|
85
|
-
)
|
75
|
+
@session.send(@reply_socket, 'kernel_info_reply', content, ident)
|
86
76
|
end
|
87
77
|
|
88
|
-
def send_status(status
|
89
|
-
@session.send(@pub_socket,
|
78
|
+
def send_status(status)
|
79
|
+
@session.send(@pub_socket, 'status', execution_state: status)
|
90
80
|
end
|
91
81
|
|
92
|
-
def execute_request(ident,
|
82
|
+
def execute_request(ident, msg)
|
93
83
|
begin
|
94
|
-
code =
|
84
|
+
code = msg[:content]['code']
|
95
85
|
rescue
|
96
|
-
STDERR.puts "Got bad
|
97
|
-
STDERR.puts parent
|
86
|
+
STDERR.puts "Got bad message: #{msg.inspect}"
|
98
87
|
return
|
99
88
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
user_expressions: {},
|
110
|
-
}
|
89
|
+
@execution_count += 1 unless msg[:content].fetch('silent', false)
|
90
|
+
send_status('busy')
|
91
|
+
@session.send(@pub_socket, 'pyin', code: code)
|
92
|
+
content = {
|
93
|
+
status: 'ok',
|
94
|
+
payload: [],
|
95
|
+
user_variables: {},
|
96
|
+
user_expressions: {},
|
97
|
+
}
|
111
98
|
result = nil
|
112
99
|
begin
|
113
|
-
$displayhook.parent = parent
|
114
|
-
$stdout.parent = parent
|
115
|
-
|
116
100
|
result = TOPLEVEL_BINDING.eval(code)
|
117
101
|
rescue Exception => e
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
exc_content = {
|
124
|
-
ename: ename,
|
125
|
-
evalue: evalue,
|
126
|
-
traceback: tb,
|
127
|
-
#etype: etype,
|
128
|
-
#status: 'error',
|
102
|
+
content = {
|
103
|
+
ename: e.class.to_s,
|
104
|
+
evalue: e.message,
|
105
|
+
etype: e.class.to_s,
|
106
|
+
traceback: ["#{RED}#{e.class}#{RESET}: #{e.message}", *e.backtrace.map { |l| "#{WHITE}#{l}#{RESET}" }],
|
129
107
|
}
|
130
|
-
|
131
|
-
@session.send(@pub_socket, 'pyerr', exc_content, parent)
|
132
|
-
|
133
|
-
reply_content = exc_content
|
108
|
+
@session.send(@pub_socket, 'pyerr', content)
|
134
109
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
#@session.send(@reply_socket, ident + reply_msg)
|
141
|
-
reply_msg = @session.send(@reply_socket, 'execute_reply', reply_content, parent, ident)
|
142
|
-
if reply_msg['content']['status'] == 'error'
|
143
|
-
abort_queue
|
144
|
-
end
|
145
|
-
if ! result.nil? and ! parent['content']['silent']
|
146
|
-
output = ResponseWithMime.new(result, TOPLEVEL_BINDING.eval("$mime_type"))
|
147
|
-
$displayhook.display(output)
|
148
|
-
end
|
149
|
-
self.send_status("idle", parent)
|
110
|
+
content[:execution_count] = @execution_count
|
111
|
+
|
112
|
+
@session.send(@reply_socket, 'execute_reply', content, ident)
|
113
|
+
display(result) if result && !msg[:content]['silent']
|
114
|
+
send_status('idle')
|
150
115
|
end
|
151
116
|
|
152
|
-
def complete_request(ident,
|
153
|
-
|
154
|
-
matches: @completer.complete(
|
117
|
+
def complete_request(ident, msg)
|
118
|
+
content = {
|
119
|
+
matches: @completer.complete(msg[:content]['line'], msg[:content]['text']),
|
155
120
|
status: 'ok',
|
156
|
-
matched_text:
|
121
|
+
matched_text: msg[:content]['line'],
|
157
122
|
}
|
158
|
-
|
159
|
-
matches, parent, ident)
|
160
|
-
return nil
|
123
|
+
@session.send(@reply_socket, 'complete_reply', content, ident)
|
161
124
|
end
|
125
|
+
end
|
162
126
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
ident, msg = @session.recv(@reply_socket, 0)
|
167
|
-
begin
|
168
|
-
handler = @handlers[msg['header']['msg_type']]
|
169
|
-
rescue
|
170
|
-
handler = nil
|
171
|
-
end
|
172
|
-
if handler.nil?
|
173
|
-
STDERR.puts "UNKNOWN MESSAGE TYPE: #{msg['header']['msg_type']} #{msg}"
|
174
|
-
else
|
175
|
-
# STDERR.puts 'handling ' + omsg.inspect
|
176
|
-
send(handler, ident, msg)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
private
|
182
|
-
RED = "\e[31m"
|
183
|
-
WHITE = "\e[37m"
|
184
|
-
RESET = "\e[0m"
|
185
|
-
|
186
|
-
def format_exception(name, value, backtrace)
|
187
|
-
tb = []
|
188
|
-
tb << "#{RED}#{name}#{RESET}: #{value}"
|
189
|
-
tb.concat(backtrace.map { |l| "#{WHITE}#{l}#{RESET}" })
|
190
|
-
tb
|
127
|
+
module Hooks
|
128
|
+
def display(obj, options={})
|
129
|
+
$iruby_kernel.display(obj, options)
|
191
130
|
end
|
192
131
|
end
|
193
132
|
end
|
133
|
+
|
134
|
+
include IRuby::Hooks
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module IRuby
|
2
|
+
# IO-like object that publishes to 0MQ socket.
|
3
|
+
class OStream
|
4
|
+
def initialize(session, socket, name)
|
5
|
+
@session = session
|
6
|
+
@socket = socket
|
7
|
+
@name = name
|
8
|
+
end
|
9
|
+
|
10
|
+
def close
|
11
|
+
@socket = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def flush
|
15
|
+
end
|
16
|
+
|
17
|
+
def isatty
|
18
|
+
false
|
19
|
+
end
|
20
|
+
alias tty? isatty
|
21
|
+
|
22
|
+
def read(*args)
|
23
|
+
raise IOError, 'not opened for reading'
|
24
|
+
end
|
25
|
+
alias next read
|
26
|
+
alias readline read
|
27
|
+
|
28
|
+
def write(s)
|
29
|
+
raise 'I/O operation on closed file' unless @socket
|
30
|
+
@session.send(@socket, 'stream', { name: @name, data: s.to_s })
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def puts(s)
|
35
|
+
write "#{s}\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
def writelines(lines)
|
39
|
+
lines.each { |s| write(s) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/iruby/session.rb
CHANGED
@@ -6,295 +6,81 @@ module IRuby
|
|
6
6
|
@username = username
|
7
7
|
@session = SecureRandom.uuid
|
8
8
|
@msg_id = 0
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@key = key
|
13
|
-
end
|
14
|
-
|
15
|
-
# Sign a message with HMAC digest. If no auth, return b''.
|
16
|
-
|
17
|
-
# Parameters
|
18
|
-
# ----------
|
19
|
-
# msg_list : list
|
20
|
-
# The [p_header,p_parent,p_content] part of the message list.
|
21
|
-
def sign(msg_list)
|
22
|
-
return '' unless @key
|
23
|
-
hmac = OpenSSL::HMAC.new(@key, @digest)
|
24
|
-
msg_list.each do |m|
|
25
|
-
hmac.update(m)
|
9
|
+
if key && sign_scheme
|
10
|
+
raise 'Unknown signature scheme' unless sign_scheme =~ /\Ahmac-(.*)\Z/
|
11
|
+
@hmac = OpenSSL::HMAC.new(key, OpenSSL::Digest::Digest.new($1))
|
26
12
|
end
|
27
|
-
hmac.hexdigest
|
28
13
|
end
|
29
14
|
|
30
|
-
|
31
|
-
|
32
|
-
|
15
|
+
# Build and send a message
|
16
|
+
def send(socket, type, content, ident=nil)
|
17
|
+
header = {
|
18
|
+
msg_type: type,
|
19
|
+
msg_id: @msg_id,
|
33
20
|
username: @username,
|
34
|
-
session:
|
21
|
+
session: @session
|
35
22
|
}
|
36
23
|
@msg_id += 1
|
37
|
-
h
|
38
|
-
end
|
39
|
-
|
40
|
-
def msg(msg_type, content=nil, parent=nil)
|
41
|
-
msg = {}
|
42
|
-
msg['header'] = msg_header
|
43
|
-
msg['parent_header'] = parent ? parent['header'] : {}
|
44
|
-
msg['metadata'] = {}
|
45
|
-
msg['header']['msg_type'] = msg_type
|
46
|
-
msg['content'] = content || {}
|
47
|
-
return msg
|
48
|
-
end
|
49
|
-
|
50
|
-
# Build and send a message via stream or socket.
|
51
|
-
#
|
52
|
-
# The message format used by this function internally is as follows:
|
53
|
-
#
|
54
|
-
# [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_content,
|
55
|
-
# buffer1,buffer2,...]
|
56
|
-
#
|
57
|
-
# The serialize/unserialize methods convert the nested message dict into this
|
58
|
-
# format.
|
59
|
-
#
|
60
|
-
# Parameters
|
61
|
-
# ----------
|
62
|
-
#
|
63
|
-
# stream : zmq.Socket or ZMQStream
|
64
|
-
# The socket-like object used to send the data.
|
65
|
-
# msg_or_type : str or Message/dict
|
66
|
-
# Normally, msg_or_type will be a msg_type unless a message is being
|
67
|
-
# sent more than once. If a header is supplied, this can be set to
|
68
|
-
# None and the msg_type will be pulled from the header.
|
69
|
-
#
|
70
|
-
# content : dict or None
|
71
|
-
# The content of the message (ignored if msg_or_type is a message).
|
72
|
-
# header : dict or None
|
73
|
-
# The header dict for the message (ignores if msg_to_type is a message).
|
74
|
-
# parent : Message or dict or None
|
75
|
-
# The parent or parent header describing the parent of this message
|
76
|
-
# (ignored if msg_or_type is a message).
|
77
|
-
# ident : bytes or list of bytes
|
78
|
-
# The zmq.IDENTITY routing path.
|
79
|
-
# subheader : dict or None
|
80
|
-
# Extra header keys for this message's header (ignored if msg_or_type
|
81
|
-
# is a message).
|
82
|
-
# buffers : list or None
|
83
|
-
# The already-serialized buffers to be appended to the message.
|
84
|
-
# track : bool
|
85
|
-
# Whether to track. Only for use with Sockets, because ZMQStream
|
86
|
-
# objects cannot track messages.
|
87
|
-
#
|
88
|
-
# Returns
|
89
|
-
# -------
|
90
|
-
# msg : dict
|
91
|
-
# The constructed message.
|
92
|
-
# (msg,tracker) : (dict, MessageTracker)
|
93
|
-
# if track=True, then a 2-tuple will be returned,
|
94
|
-
# the first element being the constructed
|
95
|
-
# message, and the second being the MessageTracker
|
96
|
-
def send(stream, msg_or_type, content=nil, parent=nil, ident=nil, buffers=nil, subheader=nil, track=false, header=nil)
|
97
|
-
if !stream.is_a?(ZMQ::Socket)
|
98
|
-
raise "stream must be Socket or ZMQSocket, not %r"%stream.class
|
99
|
-
end
|
100
|
-
|
101
|
-
if msg_or_type.is_a?(Hash)
|
102
|
-
msg = msg_or_type
|
103
|
-
else
|
104
|
-
msg = self.msg(msg_or_type, content, parent)
|
105
|
-
end
|
106
24
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
if buffers.any?
|
111
|
-
flag = ZMQ::SNDMORE
|
112
|
-
_track = false
|
113
|
-
else
|
114
|
-
_track=track
|
115
|
-
end
|
116
|
-
if track
|
117
|
-
to_send.each_with_index do |part, i|
|
118
|
-
if i == to_send.length - 1
|
119
|
-
flag = 0
|
120
|
-
else
|
121
|
-
flag = ZMQ::SNDMORE
|
122
|
-
end
|
123
|
-
stream.send_string(part, flag)
|
124
|
-
end
|
125
|
-
else
|
126
|
-
to_send.each_with_index do |part, i|
|
127
|
-
if i == to_send.length - 1
|
128
|
-
flag = 0
|
129
|
-
else
|
130
|
-
flag = ZMQ::SNDMORE
|
131
|
-
end
|
132
|
-
stream.send_string(part, flag)
|
133
|
-
end
|
25
|
+
list = serialize(header, content, ident)
|
26
|
+
list.each_with_index do |part, i|
|
27
|
+
socket.send_string(part, i == list.size - 1 ? 0 : ZMQ::SNDMORE)
|
134
28
|
end
|
135
|
-
# STDOUT.puts '-'*30
|
136
|
-
# STDOUT.puts "SENDING"
|
137
|
-
# STDOUT.puts to_send
|
138
|
-
# STDOUT.puts to_send.length
|
139
|
-
# STDOUT.puts '-'*30
|
140
|
-
|
141
|
-
#buffers.each do |b|
|
142
|
-
#stream.send(b, flag, copy=False)
|
143
|
-
#end
|
144
|
-
#if buffers:
|
145
|
-
#if track:
|
146
|
-
#tracker = stream.send(buffers[-1], copy=False, track=track)
|
147
|
-
#else:
|
148
|
-
#tracker = stream.send(buffers[-1], copy=False)
|
149
|
-
|
150
|
-
# omsg = Message(msg)
|
151
|
-
#if self.debug:
|
152
|
-
#pprint.pprint(msg)
|
153
|
-
#pprint.pprint(to_send)
|
154
|
-
#pprint.pprint(buffers)
|
155
|
-
|
156
|
-
#msg['tracker'] = tracker
|
157
|
-
|
158
|
-
return msg
|
159
29
|
end
|
160
30
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
frame = ""
|
172
|
-
rc = socket.recv_string(frame, mode)
|
173
|
-
ZMQ::Util.error_check("zmq_msg_send", rc)
|
174
|
-
msg << frame
|
175
|
-
rescue
|
176
|
-
end
|
31
|
+
# Receive a message and decode it
|
32
|
+
def recv(socket, mode)
|
33
|
+
msg = []
|
34
|
+
while msg.empty? || socket.more_parts?
|
35
|
+
begin
|
36
|
+
frame = ''
|
37
|
+
rc = socket.recv_string(frame, mode)
|
38
|
+
ZMQ::Util.error_check('zmq_msg_send', rc)
|
39
|
+
msg << frame
|
40
|
+
rescue
|
177
41
|
end
|
178
|
-
# Skip everything before DELIM, then munge the three json objects into the
|
179
|
-
# one the rest of my code expects
|
180
|
-
i = msg.index(DELIM)
|
181
|
-
idents = msg[0..i-1]
|
182
|
-
msg_list = msg[i+1..-1]
|
183
|
-
end
|
184
|
-
return idents, unserialize(msg_list)
|
185
|
-
end
|
186
|
-
|
187
|
-
# Serialize the message components to bytes.
|
188
|
-
#
|
189
|
-
# This is roughly the inverse of unserialize. The serialize/unserialize
|
190
|
-
# methods work with full message lists, whereas pack/unpack work with
|
191
|
-
# the individual message parts in the message list.
|
192
|
-
#
|
193
|
-
# Parameters
|
194
|
-
# ----------
|
195
|
-
# msg : dict or Message
|
196
|
-
# The nexted message dict as returned by the self.msg method.
|
197
|
-
#
|
198
|
-
# Returns
|
199
|
-
# -------
|
200
|
-
# msg_list : list
|
201
|
-
# The list of bytes objects to be sent with the format:
|
202
|
-
# [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_content,
|
203
|
-
# buffer1,buffer2,...]. In this list, the p_* entities are
|
204
|
-
# the packed or serialized versions, so if JSON is used, these
|
205
|
-
# are utf8 encoded JSON strings.
|
206
|
-
def serialize(msg, ident=nil)
|
207
|
-
content = msg.fetch('content', {})
|
208
|
-
if content.nil?
|
209
|
-
content = {}.to_json
|
210
|
-
elsif content.is_a?(Hash)
|
211
|
-
content = content.to_json
|
212
|
-
#elsif isinstance(content, bytes):
|
213
|
-
# content is already packed, as in a relayed message
|
214
|
-
#pass
|
215
|
-
#elsif isinstance(content, unicode):
|
216
|
-
# should be bytes, but JSON often spits out unicode
|
217
|
-
#content = content.encode('utf8')
|
218
|
-
else
|
219
|
-
raise "Content incorrect type: %s"%type(content)
|
220
|
-
end
|
221
|
-
|
222
|
-
real_message = [MultiJson.dump(msg['header']),
|
223
|
-
MultiJson.dump(msg['parent_header']),
|
224
|
-
MultiJson.dump(msg['metadata']),
|
225
|
-
MultiJson.dump(msg['content']),
|
226
|
-
]
|
227
|
-
|
228
|
-
to_send = []
|
229
|
-
|
230
|
-
if ident.is_a?(Array)
|
231
|
-
# accept list of idents
|
232
|
-
to_send += ident
|
233
|
-
elsif !ident.nil?
|
234
|
-
to_send << ident
|
235
42
|
end
|
236
|
-
to_send << DELIM
|
237
43
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
# STDOUT.puts to_send.length
|
244
|
-
|
245
|
-
return to_send
|
44
|
+
i = msg.index(DELIM)
|
45
|
+
idents, msg_list = msg[0..i-1], msg[i+1..-1]
|
46
|
+
msg = unserialize(msg_list)
|
47
|
+
@last_received_header = msg[:header]
|
48
|
+
return idents, msg
|
246
49
|
end
|
247
50
|
|
248
|
-
|
249
|
-
# This is roughly the inverse of serialize. The serialize/unserialize
|
250
|
-
# methods work with full message lists, whereas pack/unpack work with
|
251
|
-
# the individual message parts in the message list.
|
51
|
+
private
|
252
52
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
# (False).
|
261
|
-
# copy : bool (True)
|
262
|
-
# Whether to return the bytes (True), or the non-copying Message
|
263
|
-
# object in each place (False).
|
53
|
+
def serialize(header, content, ident)
|
54
|
+
msg = [MultiJson.dump(header),
|
55
|
+
MultiJson.dump(@last_received_header || {}),
|
56
|
+
'{}',
|
57
|
+
MultiJson.dump(content || {})]
|
58
|
+
([ident].flatten.compact << DELIM << sign(msg)) + msg
|
59
|
+
end
|
264
60
|
|
265
|
-
|
266
|
-
# -------
|
267
|
-
# msg : dict
|
268
|
-
# The nested message dict with top-level keys [header, parent_header,
|
269
|
-
# content, buffers].
|
270
|
-
def unserialize(msg_list, content=true, copy=true)
|
61
|
+
def unserialize(msg_list)
|
271
62
|
minlen = 5
|
272
|
-
message
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
63
|
+
raise 'malformed message, must have at least #{minlen} elements' unless msg_list.length >= minlen
|
64
|
+
s, header, parent_header, metadata, content, buffers = *msg_list
|
65
|
+
raise 'Invalid signature' unless s == sign(msg_list[1..-1])
|
66
|
+
{
|
67
|
+
header: MultiJson.load(header),
|
68
|
+
parent_header: MultiJson.load(parent_header),
|
69
|
+
metadata: MultiJson.load(metadata),
|
70
|
+
content: MultiJson.load(content),
|
71
|
+
buffers: buffers
|
72
|
+
}
|
73
|
+
end
|
283
74
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
if content
|
291
|
-
message['content'] = MultiJson.load(msg_list[4])
|
75
|
+
# Sign using HMAC
|
76
|
+
def sign(list)
|
77
|
+
if @hmac
|
78
|
+
@hmac.reset
|
79
|
+
list.each {|m| @hmac.update(m) }
|
80
|
+
@hmac.hexdigest
|
292
81
|
else
|
293
|
-
|
82
|
+
''
|
294
83
|
end
|
295
|
-
|
296
|
-
message['buffers'] = msg_list[4..-1]
|
297
|
-
return message
|
298
84
|
end
|
299
85
|
end
|
300
86
|
end
|
data/lib/iruby/version.rb
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Damián Silvani
|
@@ -85,14 +85,16 @@ files:
|
|
85
85
|
- Rakefile
|
86
86
|
- Ruby Notebook.ipynb
|
87
87
|
- attic/output/html.rb
|
88
|
+
- attic/test/helper.rb
|
89
|
+
- attic/test/html_test.rb
|
90
|
+
- attic/test/output_maps_test.rb
|
88
91
|
- bin/iruby
|
89
92
|
- iruby.gemspec
|
90
93
|
- lib/iruby.rb
|
91
94
|
- lib/iruby/command.rb
|
92
95
|
- lib/iruby/completer.rb
|
93
|
-
- lib/iruby/display_hook.rb
|
94
96
|
- lib/iruby/kernel.rb
|
95
|
-
- lib/iruby/
|
97
|
+
- lib/iruby/ostream.rb
|
96
98
|
- lib/iruby/session.rb
|
97
99
|
- lib/iruby/version.rb
|
98
100
|
- static/base/images/favicon.ico
|
@@ -101,9 +103,6 @@ files:
|
|
101
103
|
- static/base/images/src/ruby.svg
|
102
104
|
- static/custom/custom.css
|
103
105
|
- static/custom/custom.js
|
104
|
-
- test/helper.rb
|
105
|
-
- test/html_test.rb
|
106
|
-
- test/output_maps_test.rb
|
107
106
|
homepage: https://github.com/minad/iruby
|
108
107
|
licenses:
|
109
108
|
- MIT
|
@@ -124,11 +123,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
123
|
version: '0'
|
125
124
|
requirements: []
|
126
125
|
rubyforge_project:
|
127
|
-
rubygems_version: 2.0.
|
126
|
+
rubygems_version: 2.0.3
|
128
127
|
signing_key:
|
129
128
|
specification_version: 4
|
130
129
|
summary: A Ruby kernel for IPython frontends (notebook console, etc.)
|
131
|
-
test_files:
|
132
|
-
- test/helper.rb
|
133
|
-
- test/html_test.rb
|
134
|
-
- test/output_maps_test.rb
|
130
|
+
test_files: []
|
data/lib/iruby/display_hook.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
module IRuby
|
2
|
-
class DisplayHook
|
3
|
-
attr_writer :parent
|
4
|
-
|
5
|
-
def initialize kernel, session, pub_socket
|
6
|
-
@kernel = kernel
|
7
|
-
@session = session
|
8
|
-
@pub_socket = pub_socket
|
9
|
-
@parent = {}
|
10
|
-
end
|
11
|
-
|
12
|
-
def display(obj)
|
13
|
-
if obj.nil?
|
14
|
-
return
|
15
|
-
end
|
16
|
-
# STDERR.puts @kernel.user_ns
|
17
|
-
# @user_ns._ = obj
|
18
|
-
# STDERR.puts "displayhook call:"
|
19
|
-
# STDERR.puts @parent_header.inspect
|
20
|
-
#@pub_socket.send(msg.to_json)
|
21
|
-
data = {}
|
22
|
-
output = obj
|
23
|
-
data['text/plain'] = output
|
24
|
-
data[obj.mime] = output
|
25
|
-
content = {data: data, metadata: {}, execution_count: @kernel.execution_count}
|
26
|
-
@session.send(@pub_socket, 'pyout', content, @parent)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/iruby/out_stream.rb
DELETED
@@ -1,93 +0,0 @@
|
|
1
|
-
module IRuby
|
2
|
-
class OutStream
|
3
|
-
#A file like object that publishes the stream to a 0MQ PUB socket.
|
4
|
-
|
5
|
-
attr_writer :parent
|
6
|
-
|
7
|
-
def initialize session, pub_socket, name, max_buffer=200
|
8
|
-
@session = session
|
9
|
-
@pub_socket = pub_socket
|
10
|
-
@name = name
|
11
|
-
@buffer = []
|
12
|
-
@buffer_len = 0
|
13
|
-
@max_buffer = max_buffer
|
14
|
-
@parent = {}
|
15
|
-
end
|
16
|
-
|
17
|
-
def close
|
18
|
-
@pub_socket = nil
|
19
|
-
end
|
20
|
-
|
21
|
-
def flush
|
22
|
-
# STDERR.puts("flushing, parent to follow")
|
23
|
-
# STDERR.puts @parent.inspect
|
24
|
-
if @pub_socket.nil?
|
25
|
-
raise 'I/O operation on closed file'
|
26
|
-
else
|
27
|
-
if @buffer
|
28
|
-
data = @buffer.join('')
|
29
|
-
content = { name: @name, data: data }
|
30
|
-
if ! @session
|
31
|
-
return
|
32
|
-
end
|
33
|
-
# msg = @session.msg('stream', content, @parent) if @session
|
34
|
-
# FIXME: Wha?
|
35
|
-
# STDERR.puts msg.to_json
|
36
|
-
#@pub_socket.send(msg.to_json)
|
37
|
-
@session.send(@pub_socket, 'stream', content, @parent)
|
38
|
-
@buffer_len = 0
|
39
|
-
@buffer = []
|
40
|
-
nil
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def isatty
|
46
|
-
false
|
47
|
-
end
|
48
|
-
alias tty? isatty
|
49
|
-
|
50
|
-
def next
|
51
|
-
raise IOError, 'not opened for reading'
|
52
|
-
end
|
53
|
-
|
54
|
-
def read(*args)
|
55
|
-
raise IOError, 'not opened for reading'
|
56
|
-
end
|
57
|
-
alias readline read
|
58
|
-
|
59
|
-
def write(s)
|
60
|
-
if @pub_socket.nil?
|
61
|
-
raise 'I/O operation on closed file'
|
62
|
-
else
|
63
|
-
s = s.to_s
|
64
|
-
@buffer << s
|
65
|
-
@buffer_len += s.length
|
66
|
-
_maybe_send
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def puts(s)
|
71
|
-
write "#{s}\n"
|
72
|
-
end
|
73
|
-
|
74
|
-
def _maybe_send
|
75
|
-
#if self._buffer[-1].include?('\n')
|
76
|
-
flush
|
77
|
-
#end
|
78
|
-
#if @bufferlen > @max_buffer
|
79
|
-
#flush
|
80
|
-
#end
|
81
|
-
end
|
82
|
-
|
83
|
-
def writelines sequence
|
84
|
-
if @pub_socket.nil?
|
85
|
-
raise 'I/O operation on closed file'
|
86
|
-
else
|
87
|
-
sequence.each do |s|
|
88
|
-
write(s)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|