iruby 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 39e9bb6cc42415e3ab312341fd04724112d2952a
4
- data.tar.gz: 7ffcf1e9550a71da16c554dada617e81e4fa7a41
3
+ metadata.gz: 8b8da3f0c37d62309c5a113b463ab8567b1c49b9
4
+ data.tar.gz: 36649a74bcb7dbcd7c91628efc8fd37c8a6bf211
5
5
  SHA512:
6
- metadata.gz: 699ae86c55615f002265cb48b9b53463c9ce228aff076a296b2f338a74423799b3781cdada02ad848b13ad307945da3d46be78e8cbc1274280836d4acbc1c08a
7
- data.tar.gz: 34aa0376810e9e1a1f8f0f6d9dc68c10bd2d714ee690966dff6cd224ca6b3ab5068aee3009324aba7b566a2a7c8114223c2fafe9ba4e36658b3056f6634ee9db
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
- Clone this repository and run `bin/iruby_profile` to create the profile, then
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`.
@@ -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?\n",
69
- "\n",
70
- "puts \"hi\""
33
+ "Neat.new.eh?"
71
34
  ],
72
- "language": "python",
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": "python",
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
@@ -7,5 +7,4 @@ require 'iruby/version'
7
7
  require 'iruby/kernel'
8
8
  require 'iruby/completer'
9
9
  require 'iruby/session'
10
- require 'iruby/out_stream'
11
- require 'iruby/display_hook'
10
+ require 'iruby/ostream'
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
- config = MultiJson.load(File.read(config_file))
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
- class ResponseWithMime
4
- def initialize(response, mime)
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
- class Kernel
18
- def self.html(s)
19
- @output_mime = "text/html"
20
- s
21
- end
10
+ puts 'Starting the kernel'
11
+ puts config
22
12
 
23
- def execution_count
24
- @execution_count
25
- end
13
+ c = ZMQ::Context.new
26
14
 
27
- def output_mime
28
- @output_mime
29
- end
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
- # Build dict of handlers for message types
40
- @handlers = {}
41
- ['execute_request', 'complete_request', 'kernel_info_request'].each do |msg_type|
42
- @handlers[msg_type] = msg_type
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 abort_queue
47
- while true
48
- #begin
49
- ident = @reply_socket.recv(ZMQ::NOBLOCK)
50
- #rescue Exception => e
51
- #if e.errno == ZMQ::EAGAIN
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, parent)
71
- reply_content = {
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 { |x| x.to_i },
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: "ruby"
73
+ language: 'ruby'
82
74
  }
83
- reply_msg = @session.send(@reply_socket, 'kernel_info_reply',
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, parent)
89
- @session.send(@pub_socket, "status", {execution_state: status}, parent)
78
+ def send_status(status)
79
+ @session.send(@pub_socket, 'status', execution_state: status)
90
80
  end
91
81
 
92
- def execute_request(ident, parent)
82
+ def execute_request(ident, msg)
93
83
  begin
94
- code = parent['content']['code']
84
+ code = msg[:content]['code']
95
85
  rescue
96
- STDERR.puts "Got bad msg: "
97
- STDERR.puts parent
86
+ STDERR.puts "Got bad message: #{msg.inspect}"
98
87
  return
99
88
  end
100
- # pyin_msg = @session.msg()
101
- if ! parent['content'].fetch('silent', false)
102
- @execution_count += 1
103
- end
104
- self.send_status("busy", parent)
105
- @session.send(@pub_socket, 'pyin', {code: code}, parent)
106
- reply_content = {status: 'ok',
107
- payload: [],
108
- user_variables: {},
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
- # $stderr.puts e.inspect
119
- #etype, evalue, tb = sys.exc_info()
120
- ename, evalue, tb = e.class.to_s, e.message, e.backtrace
121
- tb = format_exception(ename, evalue, tb)
122
- #tb = "1, 2, 3"
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
- # STDERR.puts exc_content
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
- reply_content['execution_count'] = @execution_count
136
-
137
- # reply_msg = @session.msg('execute_reply', reply_content, parent)
138
- #$stdout.puts reply_msg
139
- #$stderr.puts reply_msg
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, parent)
153
- matches = {
154
- matches: @completer.complete(parent['content']['line'], parent['content']['text']),
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: parent['content']['line'],
121
+ matched_text: msg[:content]['line'],
157
122
  }
158
- completion_msg = @session.send(@reply_socket, 'complete_reply',
159
- matches, parent, ident)
160
- return nil
123
+ @session.send(@reply_socket, 'complete_reply', content, ident)
161
124
  end
125
+ end
162
126
 
163
- def start()
164
- self.send_status("starting", nil)
165
- while true
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
- raise 'Unknown signature scheme' unless sign_scheme =~ /\Ahmac-(.*)\Z/
11
- @digest = OpenSSL::Digest::Digest.new($1)
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
- def msg_header
31
- h = {
32
- msg_id: @msg_id,
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: @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
- buffers ||= []
108
- to_send = self.serialize(msg, ident)
109
- flag = 0
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
- def recv(socket, mode=ZMQ::NOBLOCK)
162
- begin
163
- msg = []
164
- frame = ""
165
- rc = socket.recv_string(frame, mode)
166
- ZMQ::Util.error_check("zmq_msg_send", rc)
167
-
168
- msg << frame
169
- while socket.more_parts?
170
- begin
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
- signature = self.sign(real_message)
239
- to_send << signature
240
-
241
- to_send += real_message
242
- # STDOUT.puts to_send
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
- # Unserialize a msg_list to a nested message dict.
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
- # Parameters:
254
- # -----------
255
- # msg_list : list of bytes or Message objects
256
- # The list of message parts of the form [HMAC,p_header,p_parent,
257
- # p_content,buffer1,buffer2,...].
258
- # content : bool (True)
259
- # Whether to unpack the content dict (True), or leave it packed
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
- # Returns
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
- unless copy
274
- minlen.times do |i|
275
- msg_list[i] = msg_list[i].bytes
276
- end
277
- end
278
- unless msg_list.length >= minlen
279
- raise "malformed message, must have at least %i elements"%minlen
280
- end
281
-
282
- raise "Invalid signature" unless self.sign(msg_list[1..-1]) == msg_list[0]
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
- header = msg_list[1]
285
- message['header'] = MultiJson.load(header)
286
- message['msg_id'] = header['msg_id']
287
- message['msg_type'] = header['msg_type']
288
- message['parent_header'] = MultiJson.load(msg_list[2])
289
- message['metadata'] = MultiJson.load(msg_list[3])
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
- message['content'] = msg_list[4]
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
@@ -1,3 +1,3 @@
1
1
  module IRuby
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
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.1
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/out_stream.rb
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.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: []
@@ -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
@@ -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