iruby 0.4.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ubuntu.yml +69 -0
- data/CHANGES.md +219 -0
- data/Gemfile +0 -2
- data/LICENSE +1 -1
- data/README.md +81 -62
- data/Rakefile +10 -10
- data/ci/Dockerfile.main.erb +1 -3
- data/iruby.gemspec +13 -19
- data/lib/iruby.rb +6 -6
- data/lib/iruby/backend.rb +38 -10
- data/lib/iruby/command.rb +2 -6
- data/lib/iruby/display.rb +216 -81
- data/lib/iruby/event_manager.rb +40 -0
- data/lib/iruby/formatter.rb +3 -3
- data/lib/iruby/input.rb +6 -6
- data/lib/iruby/input/autoload.rb +1 -1
- data/lib/iruby/input/builder.rb +4 -4
- data/lib/iruby/input/button.rb +2 -2
- data/lib/iruby/input/cancel.rb +1 -1
- data/lib/iruby/input/checkbox.rb +3 -3
- data/lib/iruby/input/date.rb +3 -3
- data/lib/iruby/input/field.rb +2 -2
- data/lib/iruby/input/file.rb +3 -3
- data/lib/iruby/input/form.rb +6 -6
- data/lib/iruby/input/label.rb +4 -4
- data/lib/iruby/input/multiple.rb +10 -10
- data/lib/iruby/input/popup.rb +2 -2
- data/lib/iruby/input/radio.rb +6 -6
- data/lib/iruby/input/select.rb +8 -8
- data/lib/iruby/input/textarea.rb +1 -1
- data/lib/iruby/input/widget.rb +2 -2
- data/lib/iruby/jupyter.rb +1 -0
- data/lib/iruby/kernel.rb +157 -29
- data/lib/iruby/ostream.rb +27 -10
- data/lib/iruby/session.rb +1 -0
- data/lib/iruby/session_adapter.rb +7 -3
- data/lib/iruby/session_adapter/pyzmq_adapter.rb +11 -10
- data/lib/iruby/session_adapter/test_adapter.rb +49 -0
- data/lib/iruby/utils.rb +15 -0
- data/lib/iruby/version.rb +1 -1
- data/run-test.sh +1 -1
- data/test/helper.rb +136 -0
- data/test/integration_test.rb +1 -2
- data/test/iruby/backend_test.rb +37 -0
- data/test/iruby/command_test.rb +0 -1
- data/test/iruby/display_test.rb +188 -0
- data/test/iruby/event_manager_test.rb +92 -0
- data/test/iruby/jupyter_test.rb +0 -1
- data/test/iruby/kernel_test.rb +185 -0
- data/test/iruby/mime_test.rb +50 -0
- data/test/iruby/multi_logger_test.rb +0 -4
- data/test/iruby/session_adapter/cztop_adapter_test.rb +1 -1
- data/test/iruby/session_adapter/ffirzmq_adapter_test.rb +1 -1
- data/test/iruby/session_adapter/session_adapter_test_base.rb +1 -3
- data/test/iruby/session_adapter_test.rb +42 -67
- data/test/iruby/session_test.rb +10 -15
- data/test/run-test.rb +19 -0
- metadata +74 -62
- data/.travis.yml +0 -41
- data/CHANGES +0 -143
- data/CONTRIBUTORS +0 -19
- data/lib/iruby/session/rbczmq.rb +0 -72
- data/lib/iruby/session_adapter/rbczmq_adapter.rb +0 -33
- data/test/iruby/session_adapter/rbczmq_adapter_test.rb +0 -37
- data/test/test_helper.rb +0 -48
data/lib/iruby/input/widget.rb
CHANGED
@@ -9,13 +9,13 @@ module IRuby
|
|
9
9
|
def content; widget_html; end
|
10
10
|
|
11
11
|
def self.builder method, &block
|
12
|
-
Builder.instance_eval do
|
12
|
+
Builder.instance_eval do
|
13
13
|
define_method method, &block
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
17
|
def widget_join method, *args
|
18
|
-
strings = args.map do |arg|
|
18
|
+
strings = args.map do |arg|
|
19
19
|
arg.is_a?(String) ? arg : arg.send(method)
|
20
20
|
end
|
21
21
|
strings.uniq.join("\n")
|
data/lib/iruby/jupyter.rb
CHANGED
data/lib/iruby/kernel.rb
CHANGED
@@ -1,37 +1,126 @@
|
|
1
1
|
module IRuby
|
2
|
+
ExecutionInfo = Struct.new(:raw_cell, :store_history, :silent)
|
3
|
+
|
2
4
|
class Kernel
|
3
5
|
RED = "\e[31m"
|
4
6
|
RESET = "\e[0m"
|
5
7
|
|
6
|
-
|
8
|
+
@events = EventManager.new([:initialized])
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# Return the event manager defined in the `IRuby::Kernel` class.
|
12
|
+
# This event manager can handle the following event:
|
13
|
+
#
|
14
|
+
# - `initialized`: The event occurred after the initialization of
|
15
|
+
# a `IRuby::Kernel` instance is finished
|
16
|
+
#
|
17
|
+
# @example Registering initialized event
|
18
|
+
# IRuby::Kernel.events.register(:initialized) do |result|
|
19
|
+
# STDERR.puts "IRuby kernel has been initialized"
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @see IRuby::EventManager
|
23
|
+
# @see IRuby::Kernel#events
|
24
|
+
attr_reader :events
|
25
|
+
|
26
|
+
# Returns the singleton kernel instance
|
7
27
|
attr_accessor :instance
|
8
28
|
end
|
9
29
|
|
30
|
+
# Returns a session object
|
10
31
|
attr_reader :session
|
11
32
|
|
12
|
-
|
33
|
+
EVENTS = [
|
34
|
+
:pre_execute,
|
35
|
+
:pre_run_cell,
|
36
|
+
:post_run_cell,
|
37
|
+
:post_execute
|
38
|
+
].freeze
|
39
|
+
|
40
|
+
def initialize(config_file, session_adapter_name=nil)
|
13
41
|
@config = MultiJson.load(File.read(config_file))
|
14
42
|
IRuby.logger.debug("IRuby kernel start with config #{@config}")
|
15
43
|
Kernel.instance = self
|
16
44
|
|
17
|
-
@session = Session.new(@config)
|
45
|
+
@session = Session.new(@config, session_adapter_name)
|
18
46
|
$stdout = OStream.new(@session, :stdout)
|
19
47
|
$stderr = OStream.new(@session, :stderr)
|
20
48
|
|
21
49
|
init_parent_process_poller
|
22
50
|
|
51
|
+
@events = EventManager.new(EVENTS)
|
23
52
|
@execution_count = 0
|
24
|
-
@backend =
|
53
|
+
@backend = PlainBackend.new
|
25
54
|
@running = true
|
55
|
+
|
56
|
+
self.class.events.trigger(:initialized, self)
|
26
57
|
end
|
27
58
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
59
|
+
# Returns the event manager defined in a `IRuby::Kernel` instance.
|
60
|
+
# This event manager can handle the following events:
|
61
|
+
#
|
62
|
+
# - `pre_execute`: The event occurred before running the code
|
63
|
+
#
|
64
|
+
# - `pre_run_cell`: The event occurred before running the code and
|
65
|
+
# if the code execution is not silent
|
66
|
+
#
|
67
|
+
# - `post_execute`: The event occurred after running the code
|
68
|
+
#
|
69
|
+
# - `post_run_cell`: The event occurred after running the code and
|
70
|
+
# if the code execution is not silent
|
71
|
+
#
|
72
|
+
# The callback functions of `pre_run_cell` event must take one argument
|
73
|
+
# to get an `ExecutionInfo` object.
|
74
|
+
# The callback functions of `post_run_cell` event must take one argument
|
75
|
+
# to get the result of the code execution.
|
76
|
+
#
|
77
|
+
# @example Registering post_run_cell event
|
78
|
+
# IRuby::Kernel.instance.events.register(:post_run_cell) do |result|
|
79
|
+
# STDERR.puts "The result of the last execution: %p" % result
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# @see IRuby::EventManager
|
83
|
+
# @see IRuby::ExecutionInfo
|
84
|
+
# @see IRuby::Kernel.events
|
85
|
+
attr_reader :events
|
86
|
+
|
87
|
+
# Switch the backend (interactive shell) system
|
88
|
+
#
|
89
|
+
# @param backend [:irb,:plain,:pry] Specify the backend name switched to
|
90
|
+
#
|
91
|
+
# @return [true,false] true if the switching succeeds, otherwise false
|
92
|
+
def switch_backend!(backend)
|
93
|
+
name = case backend
|
94
|
+
when String, Symbol
|
95
|
+
name = backend.downcase
|
96
|
+
else
|
97
|
+
name = backend
|
98
|
+
end
|
99
|
+
|
100
|
+
backend_class = case name
|
101
|
+
when :irb, :plain
|
102
|
+
PlainBackend
|
103
|
+
when :pry
|
104
|
+
PryBackend
|
105
|
+
else
|
106
|
+
raise ArgumentError,
|
107
|
+
"Unknown backend name: %p" % backend
|
108
|
+
end
|
109
|
+
|
110
|
+
begin
|
111
|
+
new_backend = backend_class.new
|
112
|
+
@backend = new_backend
|
113
|
+
true
|
114
|
+
rescue Exception => e
|
115
|
+
unless LoadError === e
|
116
|
+
IRuby.logger.warn "Could not load #{backend_class}: " +
|
117
|
+
"#{e.message}\n#{e.backtrace.join("\n")}"
|
118
|
+
end
|
119
|
+
return false
|
120
|
+
end
|
33
121
|
end
|
34
122
|
|
123
|
+
# @private
|
35
124
|
def run
|
36
125
|
send_status :starting
|
37
126
|
while @running
|
@@ -39,6 +128,7 @@ module IRuby
|
|
39
128
|
end
|
40
129
|
end
|
41
130
|
|
131
|
+
# @private
|
42
132
|
def dispatch
|
43
133
|
msg = @session.recv(:reply)
|
44
134
|
IRuby.logger.debug "Kernel#dispatch: msg = #{msg}"
|
@@ -55,29 +145,51 @@ module IRuby
|
|
55
145
|
@session.send(:publish, :error, error_content(e))
|
56
146
|
end
|
57
147
|
|
148
|
+
# @private
|
58
149
|
def kernel_info_request(msg)
|
59
150
|
@session.send(:reply, :kernel_info_reply,
|
60
151
|
protocol_version: '5.0',
|
61
152
|
implementation: 'iruby',
|
62
|
-
banner: "IRuby #{IRuby::VERSION} (with #{@session.description})",
|
63
153
|
implementation_version: IRuby::VERSION,
|
64
154
|
language_info: {
|
65
155
|
name: 'ruby',
|
66
156
|
version: RUBY_VERSION,
|
67
157
|
mimetype: 'application/x-ruby',
|
68
158
|
file_extension: '.rb'
|
69
|
-
}
|
159
|
+
},
|
160
|
+
banner: "IRuby #{IRuby::VERSION} (with #{@session.description})",
|
161
|
+
help_links: [
|
162
|
+
{
|
163
|
+
text: "Ruby Documentation",
|
164
|
+
url: "https://ruby-doc.org/"
|
165
|
+
}
|
166
|
+
],
|
167
|
+
status: :ok)
|
70
168
|
end
|
71
169
|
|
170
|
+
# @private
|
72
171
|
def send_status(status)
|
73
172
|
IRuby.logger.debug "Send status: #{status}"
|
74
173
|
@session.send(:publish, :status, execution_state: status)
|
75
174
|
end
|
76
175
|
|
176
|
+
# @private
|
77
177
|
def execute_request(msg)
|
78
178
|
code = msg[:content]['code']
|
79
|
-
|
80
|
-
|
179
|
+
store_history = msg[:content]['store_history']
|
180
|
+
silent = msg[:content]['silent']
|
181
|
+
|
182
|
+
@execution_count += 1 if store_history
|
183
|
+
|
184
|
+
unless silent
|
185
|
+
@session.send(:publish, :execute_input, code: code, execution_count: @execution_count)
|
186
|
+
end
|
187
|
+
|
188
|
+
events.trigger(:pre_execute)
|
189
|
+
unless silent
|
190
|
+
exec_info = ExecutionInfo.new(code, store_history, silent)
|
191
|
+
events.trigger(:pre_run_cell, exec_info)
|
192
|
+
end
|
81
193
|
|
82
194
|
content = {
|
83
195
|
status: :ok,
|
@@ -85,35 +197,49 @@ module IRuby
|
|
85
197
|
user_expressions: {},
|
86
198
|
execution_count: @execution_count
|
87
199
|
}
|
200
|
+
|
88
201
|
result = nil
|
89
202
|
begin
|
90
|
-
result = @backend.eval(code,
|
203
|
+
result = @backend.eval(code, store_history)
|
91
204
|
rescue SystemExit
|
92
205
|
content[:payload] << { source: :ask_exit }
|
93
206
|
rescue Exception => e
|
94
207
|
content = error_content(e)
|
95
208
|
@session.send(:publish, :error, content)
|
96
209
|
content[:status] = :error
|
210
|
+
content[:execution_count] = @execution_count
|
97
211
|
end
|
212
|
+
|
213
|
+
unless result.nil? || silent
|
214
|
+
@session.send(:publish, :execute_result,
|
215
|
+
data: Display.display(result),
|
216
|
+
metadata: {},
|
217
|
+
execution_count: @execution_count)
|
218
|
+
end
|
219
|
+
|
220
|
+
events.trigger(:post_execute)
|
221
|
+
events.trigger(:post_run_cell, result) unless silent
|
222
|
+
|
98
223
|
@session.send(:reply, :execute_reply, content)
|
99
|
-
@session.send(:publish, :execute_result,
|
100
|
-
data: Display.display(result),
|
101
|
-
metadata: {},
|
102
|
-
execution_count: @execution_count) unless result.nil? || msg[:content]['silent']
|
103
224
|
end
|
104
225
|
|
226
|
+
# @private
|
105
227
|
def error_content(e)
|
228
|
+
rindex = e.backtrace.rindex{|line| line.start_with?(@backend.eval_path)} || -1
|
229
|
+
backtrace = SyntaxError === e && rindex == -1 ? [] : e.backtrace[0..rindex]
|
106
230
|
{ ename: e.class.to_s,
|
107
231
|
evalue: e.message,
|
108
|
-
traceback: ["#{RED}#{e.class}#{RESET}: #{e.message}", *
|
232
|
+
traceback: ["#{RED}#{e.class}#{RESET}: #{e.message}", *backtrace] }
|
109
233
|
end
|
110
234
|
|
235
|
+
# @private
|
111
236
|
def is_complete_request(msg)
|
112
237
|
# FIXME: the code completeness should be judged by using ripper or other Ruby parser
|
113
238
|
@session.send(:reply, :is_complete_reply,
|
114
239
|
status: :unknown)
|
115
240
|
end
|
116
241
|
|
242
|
+
# @private
|
117
243
|
def complete_request(msg)
|
118
244
|
# HACK for #26, only complete last line
|
119
245
|
code = msg[:content]['code']
|
@@ -123,47 +249,49 @@ module IRuby
|
|
123
249
|
end
|
124
250
|
@session.send(:reply, :complete_reply,
|
125
251
|
matches: @backend.complete(code),
|
126
|
-
status: :ok,
|
127
252
|
cursor_start: start.to_i,
|
128
|
-
cursor_end: msg[:content]['cursor_pos']
|
253
|
+
cursor_end: msg[:content]['cursor_pos'],
|
254
|
+
metadata: {},
|
255
|
+
status: :ok)
|
129
256
|
end
|
130
257
|
|
258
|
+
# @private
|
131
259
|
def connect_request(msg)
|
132
260
|
@session.send(:reply, :connect_reply, Hash[%w(shell_port iopub_port stdin_port hb_port).map {|k| [k, @config[k]] }])
|
133
261
|
end
|
134
262
|
|
263
|
+
# @private
|
135
264
|
def shutdown_request(msg)
|
136
265
|
@session.send(:reply, :shutdown_reply, msg[:content])
|
137
266
|
@running = false
|
138
267
|
end
|
139
268
|
|
269
|
+
# @private
|
140
270
|
def history_request(msg)
|
141
271
|
# we will just send back empty history for now, pending clarification
|
142
272
|
# as requested in ipython/ipython#3806
|
143
273
|
@session.send(:reply, :history_reply, history: [])
|
144
274
|
end
|
145
275
|
|
276
|
+
# @private
|
146
277
|
def inspect_request(msg)
|
147
|
-
|
148
|
-
@session.send(:reply, :inspect_reply,
|
149
|
-
status: :ok,
|
150
|
-
data: Display.display(result),
|
151
|
-
metadata: {})
|
152
|
-
rescue Exception => e
|
153
|
-
IRuby.logger.warn "Inspection error: #{e.message}\n#{e.backtrace.join("\n")}"
|
154
|
-
@session.send(:reply, :inspect_reply, status: :error)
|
278
|
+
# not yet implemented. See (#119).
|
279
|
+
@session.send(:reply, :inspect_reply, status: :ok, found: false, data: {}, metadata: {})
|
155
280
|
end
|
156
281
|
|
282
|
+
# @private
|
157
283
|
def comm_open(msg)
|
158
284
|
comm_id = msg[:content]['comm_id']
|
159
285
|
target_name = msg[:content]['target_name']
|
160
286
|
Comm.comm[comm_id] = Comm.target[target_name].new(target_name, comm_id)
|
161
287
|
end
|
162
288
|
|
289
|
+
# @private
|
163
290
|
def comm_msg(msg)
|
164
291
|
Comm.comm[msg[:content]['comm_id']].handle_msg(msg[:content]['data'])
|
165
292
|
end
|
166
293
|
|
294
|
+
# @private
|
167
295
|
def comm_close(msg)
|
168
296
|
comm_id = msg[:content]['comm_id']
|
169
297
|
Comm.comm[comm_id].handle_close(msg[:content]['data'])
|
data/lib/iruby/ostream.rb
CHANGED
@@ -25,26 +25,43 @@ module IRuby
|
|
25
25
|
alias_method :next, :read
|
26
26
|
alias_method :readline, :read
|
27
27
|
|
28
|
-
def write(
|
29
|
-
|
30
|
-
|
31
|
-
nil
|
28
|
+
def write(*obj)
|
29
|
+
str = build_string { |sio| sio.write(*obj) }
|
30
|
+
session_send(str)
|
32
31
|
end
|
33
32
|
alias_method :<<, :write
|
34
33
|
alias_method :print, :write
|
35
34
|
|
36
|
-
def printf(*
|
37
|
-
|
35
|
+
def printf(format, *obj)
|
36
|
+
str = build_string { |sio| sio.printf(format, *obj) }
|
37
|
+
session_send(str)
|
38
38
|
end
|
39
39
|
|
40
|
-
def puts(*
|
41
|
-
|
42
|
-
|
43
|
-
nil
|
40
|
+
def puts(*obj)
|
41
|
+
str = build_string { |sio| sio.puts(*obj) }
|
42
|
+
session_send(str)
|
44
43
|
end
|
45
44
|
|
46
45
|
def writelines(lines)
|
47
46
|
lines.each { |s| write(s) }
|
48
47
|
end
|
48
|
+
|
49
|
+
# Called by irb
|
50
|
+
def set_encoding(extern, intern)
|
51
|
+
a = extern
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def build_string
|
57
|
+
StringIO.open { |sio| yield(sio); sio.string }
|
58
|
+
end
|
59
|
+
|
60
|
+
def session_send(str)
|
61
|
+
raise 'I/O operation on closed file' unless @session
|
62
|
+
|
63
|
+
@session.send(:publish, :stream, name: @name, text: str)
|
64
|
+
nil
|
65
|
+
end
|
49
66
|
end
|
50
67
|
end
|
data/lib/iruby/session.rb
CHANGED
@@ -10,6 +10,10 @@ module IRuby
|
|
10
10
|
false
|
11
11
|
end
|
12
12
|
|
13
|
+
def self.load_requirements
|
14
|
+
# Do nothing
|
15
|
+
end
|
16
|
+
|
13
17
|
def initialize(config)
|
14
18
|
@config = config
|
15
19
|
end
|
@@ -36,15 +40,15 @@ module IRuby
|
|
36
40
|
|
37
41
|
require_relative 'session_adapter/ffirzmq_adapter'
|
38
42
|
require_relative 'session_adapter/cztop_adapter'
|
39
|
-
require_relative 'session_adapter/rbczmq_adapter'
|
40
43
|
require_relative 'session_adapter/pyzmq_adapter'
|
44
|
+
require_relative 'session_adapter/test_adapter'
|
41
45
|
|
42
46
|
def self.select_adapter_class(name=nil)
|
43
47
|
classes = {
|
44
48
|
'ffi-rzmq' => SessionAdapter::FfirzmqAdapter,
|
45
49
|
'cztop' => SessionAdapter::CztopAdapter,
|
46
|
-
'
|
47
|
-
'
|
50
|
+
# 'pyzmq' => SessionAdapter::PyzmqAdapter
|
51
|
+
'test' => SessionAdapter::TestAdapter,
|
48
52
|
}
|
49
53
|
if (name ||= ENV.fetch('IRUBY_SESSION_ADAPTER', nil))
|
50
54
|
cls = classes[name]
|
@@ -1,14 +1,19 @@
|
|
1
1
|
module IRuby
|
2
2
|
module SessionAdapter
|
3
3
|
class PyzmqAdapter < BaseAdapter
|
4
|
-
def self.load_requirements
|
5
|
-
require 'pycall'
|
6
|
-
@zmq = PyCall.import_module('zmq')
|
7
|
-
rescue PyCall::PyError => error
|
8
|
-
raise LoadError, error.message
|
9
|
-
end
|
10
4
|
|
11
5
|
class << self
|
6
|
+
def load_requirements
|
7
|
+
require 'pycall'
|
8
|
+
import_pyzmq
|
9
|
+
end
|
10
|
+
|
11
|
+
def import_pyzmq
|
12
|
+
@zmq = PyCall.import_module('zmq')
|
13
|
+
rescue PyCall::PyError => error
|
14
|
+
raise LoadError, error.message
|
15
|
+
end
|
16
|
+
|
12
17
|
attr_reader :zmq
|
13
18
|
end
|
14
19
|
|
@@ -20,10 +25,6 @@ module IRuby
|
|
20
25
|
make_socket(:PUB, protocol, host, port)
|
21
26
|
end
|
22
27
|
|
23
|
-
def make_pub_socket(protocol, host, port)
|
24
|
-
make_socket(:REP, protocol, host, port)
|
25
|
-
end
|
26
|
-
|
27
28
|
def heartbeat_loop(sock)
|
28
29
|
PyCall.sys.path.append(File.expand_path('../pyzmq', __FILE__))
|
29
30
|
heartbeat = PyCall.import_module('iruby.heartbeat')
|