iruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +70 -0
- data/Rakefile +13 -0
- data/Ruby Notebook.ipynb +180 -0
- data/attic/output/html.rb +217 -0
- data/bin/iruby +6 -0
- data/iruby.gemspec +26 -0
- data/lib/iruby.rb +11 -0
- data/lib/iruby/command.rb +103 -0
- data/lib/iruby/completer.rb +19 -0
- data/lib/iruby/display_hook.rb +29 -0
- data/lib/iruby/kernel.rb +193 -0
- data/lib/iruby/out_stream.rb +93 -0
- data/lib/iruby/session.rb +300 -0
- data/lib/iruby/version.rb +3 -0
- data/static/base/images/favicon.ico +0 -0
- data/static/base/images/ipynblogo.png +0 -0
- data/static/base/images/src/ipynblogo.svg +768 -0
- data/static/base/images/src/ruby.svg +948 -0
- data/static/custom/custom.css +40 -0
- data/static/custom/custom.js +2 -0
- data/test/helper.rb +5 -0
- data/test/html_test.rb +17 -0
- data/test/output_maps_test.rb +110 -0
- metadata +134 -0
data/bin/iruby
ADDED
data/iruby.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require File.dirname(__FILE__) + '/lib/iruby/version'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'iruby'
|
7
|
+
s.date = Date.today.to_s
|
8
|
+
s.version = IRuby::VERSION
|
9
|
+
s.authors = ['Damián Silvani', 'Min RK', 'martin sarsale', 'Josh Adams', 'Daniel Mendler']
|
10
|
+
s.email = ['benjaminrk@gmail.com']
|
11
|
+
s.description = 'Ruby Kernel for IPython'
|
12
|
+
s.summary = 'A Ruby kernel for IPython frontends (notebook console, etc.)'
|
13
|
+
s.homepage = 'https://github.com/minad/iruby'
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split($/)
|
17
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
s.test_files = s.files.grep(%r{^test/})
|
19
|
+
s.require_paths = %w(lib)
|
20
|
+
|
21
|
+
s.add_development_dependency 'rake'
|
22
|
+
|
23
|
+
s.add_runtime_dependency 'bond'
|
24
|
+
s.add_runtime_dependency 'ffi-rzmq'
|
25
|
+
s.add_runtime_dependency 'multi_json'
|
26
|
+
end
|
data/lib/iruby.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'ffi-rzmq'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'multi_json'
|
4
|
+
require 'bond'
|
5
|
+
require 'openssl'
|
6
|
+
require 'iruby/version'
|
7
|
+
require 'iruby/kernel'
|
8
|
+
require 'iruby/completer'
|
9
|
+
require 'iruby/session'
|
10
|
+
require 'iruby/out_stream'
|
11
|
+
require 'iruby/display_hook'
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
|
3
|
+
module IRuby
|
4
|
+
class Command
|
5
|
+
IRUBYDIR = '~/.config/iruby'
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@args = args
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
raise 'Use --iruby-dir instead of --ipython-dir!' unless @args.grep(/\A--ipython-dir=.*\Z/).empty?
|
13
|
+
|
14
|
+
if @args.first == 'kernel'
|
15
|
+
run_kernel
|
16
|
+
else
|
17
|
+
run_ipython
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def run_kernel
|
24
|
+
config_file, boot_file, working_dir = @args[1..-1]
|
25
|
+
Dir.chdir(working_dir) if working_dir
|
26
|
+
require boot_file if boot_file
|
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
|
61
|
+
end
|
62
|
+
|
63
|
+
def run_ipython
|
64
|
+
dir = @args.grep(/\A--iruby-dir=.*\Z/)
|
65
|
+
@args -= dir
|
66
|
+
dir = dir.last.to_s.sub(/\A--profile=/, '')
|
67
|
+
dir = ENV['IRUBYDIR'] || IRUBYDIR if dir.empty?
|
68
|
+
dir = File.expand_path(dir)
|
69
|
+
ENV['IPYTHONDIR'] = dir
|
70
|
+
|
71
|
+
if @args.size == 3 && @args[0] == 'profile' && @args[1] == 'create'
|
72
|
+
profile = @args[2]
|
73
|
+
else
|
74
|
+
profile = @args.grep(/\A--profile=.*\Z/).last.to_s.sub(/\A--profile=/, '')
|
75
|
+
profile = 'default' if profile.empty?
|
76
|
+
end
|
77
|
+
|
78
|
+
create_profile(dir, profile)
|
79
|
+
Kernel.exec('ipython', *@args)
|
80
|
+
end
|
81
|
+
|
82
|
+
def create_profile(dir, profile)
|
83
|
+
profile_dir = File.join(dir, "profile_#{profile}")
|
84
|
+
unless File.directory?(profile_dir)
|
85
|
+
puts "Creating profile directory #{profile_dir}"
|
86
|
+
system("ipython profile create #{Shellwords.escape profile}")
|
87
|
+
end
|
88
|
+
|
89
|
+
kernel_cmd = "c.KernelManager.kernel_cmd = ['#{File.expand_path $0}', 'kernel', '{connection_file}']"
|
90
|
+
Dir[File.join(profile_dir, '*_config.py')].each do |path|
|
91
|
+
File.open(path, 'r+') do |f|
|
92
|
+
content = f.read
|
93
|
+
content << kernel_cmd unless content.gsub!(/^c\.KernelManager\.kernel_cmd.*$/, kernel_cmd)
|
94
|
+
f.pos = 0
|
95
|
+
f.write(content)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
static_dir = File.join(profile_dir, 'static')
|
100
|
+
File.symlink(File.join(File.dirname(__FILE__), '..', '..', 'static'), static_dir) unless File.exists?(static_dir)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module IRuby
|
2
|
+
class Completer
|
3
|
+
module Readline
|
4
|
+
class<< self
|
5
|
+
attr_accessor :line_buffer
|
6
|
+
def setup(arg); end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
Bond.start(readline: Readline, debug: true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def complete(line, text)
|
15
|
+
Readline.line_buffer = line
|
16
|
+
Bond.agent.call(line)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
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/kernel.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
module IRuby
|
2
|
+
|
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
|
16
|
+
|
17
|
+
class Kernel
|
18
|
+
def self.html(s)
|
19
|
+
@output_mime = "text/html"
|
20
|
+
s
|
21
|
+
end
|
22
|
+
|
23
|
+
def execution_count
|
24
|
+
@execution_count
|
25
|
+
end
|
26
|
+
|
27
|
+
def output_mime
|
28
|
+
@output_mime
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize session, reply_socket, pub_socket
|
32
|
+
@session = session
|
33
|
+
@reply_socket = reply_socket
|
34
|
+
@pub_socket = pub_socket
|
35
|
+
@history = []
|
36
|
+
@execution_count = 0
|
37
|
+
@completer = Completer.new
|
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
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
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)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def kernel_info_request(ident, parent)
|
71
|
+
reply_content = {
|
72
|
+
protocol_version: [4, 0],
|
73
|
+
|
74
|
+
# Language version number (mandatory).
|
75
|
+
# It is Python version number (e.g., [2, 7, 3]) for the kernel
|
76
|
+
# included in IPython.
|
77
|
+
language_version: RUBY_VERSION.split('.').map { |x| x.to_i },
|
78
|
+
|
79
|
+
# Programming language in which kernel is implemented (mandatory).
|
80
|
+
# Kernel included in IPython returns 'python'.
|
81
|
+
language: "ruby"
|
82
|
+
}
|
83
|
+
reply_msg = @session.send(@reply_socket, 'kernel_info_reply',
|
84
|
+
reply_content, parent, ident
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def send_status(status, parent)
|
89
|
+
@session.send(@pub_socket, "status", {execution_state: status}, parent)
|
90
|
+
end
|
91
|
+
|
92
|
+
def execute_request(ident, parent)
|
93
|
+
begin
|
94
|
+
code = parent['content']['code']
|
95
|
+
rescue
|
96
|
+
STDERR.puts "Got bad msg: "
|
97
|
+
STDERR.puts parent
|
98
|
+
return
|
99
|
+
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
|
+
}
|
111
|
+
result = nil
|
112
|
+
begin
|
113
|
+
$displayhook.parent = parent
|
114
|
+
$stdout.parent = parent
|
115
|
+
|
116
|
+
result = TOPLEVEL_BINDING.eval(code)
|
117
|
+
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',
|
129
|
+
}
|
130
|
+
# STDERR.puts exc_content
|
131
|
+
@session.send(@pub_socket, 'pyerr', exc_content, parent)
|
132
|
+
|
133
|
+
reply_content = exc_content
|
134
|
+
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)
|
150
|
+
end
|
151
|
+
|
152
|
+
def complete_request(ident, parent)
|
153
|
+
matches = {
|
154
|
+
matches: @completer.complete(parent['content']['line'], parent['content']['text']),
|
155
|
+
status: 'ok',
|
156
|
+
matched_text: parent['content']['line'],
|
157
|
+
}
|
158
|
+
completion_msg = @session.send(@reply_socket, 'complete_reply',
|
159
|
+
matches, parent, ident)
|
160
|
+
return nil
|
161
|
+
end
|
162
|
+
|
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
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,93 @@
|
|
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
|