iruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + '/../lib'
4
+ require 'iruby/command'
5
+
6
+ IRuby::Command.new(ARGV).run
@@ -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
@@ -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
@@ -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