iruby 0.4.0 → 0.7.1
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 +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')
|