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 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