iruby 0.5.0 → 0.7.2
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 +9 -2
- data/CHANGES.md +226 -0
- data/README.md +17 -2
- data/iruby.gemspec +1 -1
- data/lib/iruby.rb +4 -0
- data/lib/iruby/backend.rb +32 -7
- data/lib/iruby/command.rb +2 -6
- data/lib/iruby/display.rb +178 -59
- data/lib/iruby/event_manager.rb +40 -0
- data/lib/iruby/kernel.rb +139 -16
- data/lib/iruby/ostream.rb +5 -0
- data/lib/iruby/session.rb +1 -0
- data/lib/iruby/session_adapter.rb +6 -0
- data/lib/iruby/session_adapter/test_adapter.rb +49 -0
- data/lib/iruby/utils.rb +12 -1
- data/lib/iruby/version.rb +1 -1
- data/test/helper.rb +46 -0
- data/test/iruby/backend_test.rb +12 -0
- data/test/iruby/display_test.rb +188 -0
- data/test/iruby/event_manager_test.rb +92 -0
- data/test/iruby/kernel_test.rb +185 -0
- data/test/iruby/mime_test.rb +25 -7
- data/test/iruby/multi_logger_test.rb +0 -3
- data/test/iruby/session_test.rb +1 -0
- data/test/iruby/utils_test.rb +25 -0
- data/test/run-test.rb +1 -0
- metadata +22 -12
- data/CHANGES +0 -167
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module IRuby
|
|
2
|
+
class EventManager
|
|
3
|
+
def initialize(available_events)
|
|
4
|
+
@available_events = available_events.dup.freeze
|
|
5
|
+
@callbacks = available_events.map {|n| [n, []] }.to_h
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
attr_reader :available_events
|
|
9
|
+
|
|
10
|
+
def register(event, &block)
|
|
11
|
+
check_available_event(event)
|
|
12
|
+
@callbacks[event] << block unless block.nil?
|
|
13
|
+
block
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def unregister(event, callback)
|
|
17
|
+
check_available_event(event)
|
|
18
|
+
val = @callbacks[event].delete(callback)
|
|
19
|
+
unless val
|
|
20
|
+
raise ArgumentError,
|
|
21
|
+
"Given callable object #{callback} is not registered as a #{event} callback"
|
|
22
|
+
end
|
|
23
|
+
val
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def trigger(event, *args, **kwargs)
|
|
27
|
+
check_available_event(event)
|
|
28
|
+
@callbacks[event].each do |fn|
|
|
29
|
+
fn.call(*args, **kwargs)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def check_available_event(event)
|
|
36
|
+
return if @callbacks.key?(event)
|
|
37
|
+
raise ArgumentError, "Unknown event name: #{event}", caller
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
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,6 +145,7 @@ 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',
|
|
@@ -76,15 +167,29 @@ module IRuby
|
|
|
76
167
|
status: :ok)
|
|
77
168
|
end
|
|
78
169
|
|
|
170
|
+
# @private
|
|
79
171
|
def send_status(status)
|
|
80
172
|
IRuby.logger.debug "Send status: #{status}"
|
|
81
173
|
@session.send(:publish, :status, execution_state: status)
|
|
82
174
|
end
|
|
83
175
|
|
|
176
|
+
# @private
|
|
84
177
|
def execute_request(msg)
|
|
85
178
|
code = msg[:content]['code']
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
88
193
|
|
|
89
194
|
content = {
|
|
90
195
|
status: :ok,
|
|
@@ -92,9 +197,10 @@ module IRuby
|
|
|
92
197
|
user_expressions: {},
|
|
93
198
|
execution_count: @execution_count
|
|
94
199
|
}
|
|
200
|
+
|
|
95
201
|
result = nil
|
|
96
202
|
begin
|
|
97
|
-
result = @backend.eval(code,
|
|
203
|
+
result = @backend.eval(code, store_history)
|
|
98
204
|
rescue SystemExit
|
|
99
205
|
content[:payload] << { source: :ask_exit }
|
|
100
206
|
rescue Exception => e
|
|
@@ -103,13 +209,21 @@ module IRuby
|
|
|
103
209
|
content[:status] = :error
|
|
104
210
|
content[:execution_count] = @execution_count
|
|
105
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
|
+
|
|
106
223
|
@session.send(:reply, :execute_reply, content)
|
|
107
|
-
@session.send(:publish, :execute_result,
|
|
108
|
-
data: Display.display(result),
|
|
109
|
-
metadata: {},
|
|
110
|
-
execution_count: @execution_count) unless result.nil? || msg[:content]['silent']
|
|
111
224
|
end
|
|
112
225
|
|
|
226
|
+
# @private
|
|
113
227
|
def error_content(e)
|
|
114
228
|
rindex = e.backtrace.rindex{|line| line.start_with?(@backend.eval_path)} || -1
|
|
115
229
|
backtrace = SyntaxError === e && rindex == -1 ? [] : e.backtrace[0..rindex]
|
|
@@ -118,12 +232,14 @@ module IRuby
|
|
|
118
232
|
traceback: ["#{RED}#{e.class}#{RESET}: #{e.message}", *backtrace] }
|
|
119
233
|
end
|
|
120
234
|
|
|
235
|
+
# @private
|
|
121
236
|
def is_complete_request(msg)
|
|
122
237
|
# FIXME: the code completeness should be judged by using ripper or other Ruby parser
|
|
123
238
|
@session.send(:reply, :is_complete_reply,
|
|
124
239
|
status: :unknown)
|
|
125
240
|
end
|
|
126
241
|
|
|
242
|
+
# @private
|
|
127
243
|
def complete_request(msg)
|
|
128
244
|
# HACK for #26, only complete last line
|
|
129
245
|
code = msg[:content]['code']
|
|
@@ -139,36 +255,43 @@ module IRuby
|
|
|
139
255
|
status: :ok)
|
|
140
256
|
end
|
|
141
257
|
|
|
258
|
+
# @private
|
|
142
259
|
def connect_request(msg)
|
|
143
260
|
@session.send(:reply, :connect_reply, Hash[%w(shell_port iopub_port stdin_port hb_port).map {|k| [k, @config[k]] }])
|
|
144
261
|
end
|
|
145
262
|
|
|
263
|
+
# @private
|
|
146
264
|
def shutdown_request(msg)
|
|
147
265
|
@session.send(:reply, :shutdown_reply, msg[:content])
|
|
148
266
|
@running = false
|
|
149
267
|
end
|
|
150
268
|
|
|
269
|
+
# @private
|
|
151
270
|
def history_request(msg)
|
|
152
271
|
# we will just send back empty history for now, pending clarification
|
|
153
272
|
# as requested in ipython/ipython#3806
|
|
154
273
|
@session.send(:reply, :history_reply, history: [])
|
|
155
274
|
end
|
|
156
275
|
|
|
276
|
+
# @private
|
|
157
277
|
def inspect_request(msg)
|
|
158
278
|
# not yet implemented. See (#119).
|
|
159
279
|
@session.send(:reply, :inspect_reply, status: :ok, found: false, data: {}, metadata: {})
|
|
160
280
|
end
|
|
161
281
|
|
|
282
|
+
# @private
|
|
162
283
|
def comm_open(msg)
|
|
163
284
|
comm_id = msg[:content]['comm_id']
|
|
164
285
|
target_name = msg[:content]['target_name']
|
|
165
286
|
Comm.comm[comm_id] = Comm.target[target_name].new(target_name, comm_id)
|
|
166
287
|
end
|
|
167
288
|
|
|
289
|
+
# @private
|
|
168
290
|
def comm_msg(msg)
|
|
169
291
|
Comm.comm[msg[:content]['comm_id']].handle_msg(msg[:content]['data'])
|
|
170
292
|
end
|
|
171
293
|
|
|
294
|
+
# @private
|
|
172
295
|
def comm_close(msg)
|
|
173
296
|
comm_id = msg[:content]['comm_id']
|
|
174
297
|
Comm.comm[comm_id].handle_close(msg[:content]['data'])
|
data/lib/iruby/ostream.rb
CHANGED
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
|
|
@@ -37,12 +41,14 @@ module IRuby
|
|
|
37
41
|
require_relative 'session_adapter/ffirzmq_adapter'
|
|
38
42
|
require_relative 'session_adapter/cztop_adapter'
|
|
39
43
|
require_relative 'session_adapter/pyzmq_adapter'
|
|
44
|
+
require_relative 'session_adapter/test_adapter'
|
|
40
45
|
|
|
41
46
|
def self.select_adapter_class(name=nil)
|
|
42
47
|
classes = {
|
|
43
48
|
'ffi-rzmq' => SessionAdapter::FfirzmqAdapter,
|
|
44
49
|
'cztop' => SessionAdapter::CztopAdapter,
|
|
45
50
|
# 'pyzmq' => SessionAdapter::PyzmqAdapter
|
|
51
|
+
'test' => SessionAdapter::TestAdapter,
|
|
46
52
|
}
|
|
47
53
|
if (name ||= ENV.fetch('IRUBY_SESSION_ADAPTER', nil))
|
|
48
54
|
cls = classes[name]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'iruby/session/mixin'
|
|
2
|
+
|
|
3
|
+
module IRuby
|
|
4
|
+
module SessionAdapter
|
|
5
|
+
class TestAdapter < BaseAdapter
|
|
6
|
+
include IRuby::SessionSerialize
|
|
7
|
+
|
|
8
|
+
DummySocket = Struct.new(:type, :protocol, :host, :port)
|
|
9
|
+
|
|
10
|
+
def initialize(config)
|
|
11
|
+
super
|
|
12
|
+
|
|
13
|
+
unless config['key'].empty? || config['signature_scheme'].empty?
|
|
14
|
+
unless config['signature_scheme'] =~ /\Ahmac-/
|
|
15
|
+
raise "Unknown signature_scheme: #{config['signature_scheme']}"
|
|
16
|
+
end
|
|
17
|
+
digest_algorithm = config['signature_scheme'][/\Ahmac-(.*)\Z/, 1]
|
|
18
|
+
@hmac = OpenSSL::HMAC.new(config['key'], OpenSSL::Digest.new(digest_algorithm))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
@send_callback = nil
|
|
22
|
+
@recv_callback = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_accessor :send_callback, :recv_callback
|
|
26
|
+
|
|
27
|
+
def send(sock, data)
|
|
28
|
+
unless @send_callback.nil?
|
|
29
|
+
@send_callback.call(sock, unserialize(data))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def recv(sock)
|
|
34
|
+
unless @recv_callback.nil?
|
|
35
|
+
serialize(@recv_callback.call(sock))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def heartbeat_loop(sock)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def make_socket(type, protocol, host, port)
|
|
45
|
+
DummySocket.new(type, protocol, host, port)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/iruby/utils.rb
CHANGED
|
@@ -4,37 +4,48 @@ module IRuby
|
|
|
4
4
|
Display.convert(object, options)
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
+
# Display the object
|
|
7
8
|
def display(obj, options = {})
|
|
8
9
|
Kernel.instance.session.send(:publish, :display_data,
|
|
9
10
|
data: Display.display(obj, options),
|
|
10
11
|
metadata: {}) unless obj.nil?
|
|
12
|
+
# The next `nil` is necessary to prevent unintentional displaying
|
|
13
|
+
# the result of Session#send
|
|
14
|
+
nil
|
|
11
15
|
end
|
|
12
16
|
|
|
17
|
+
# Clear the output area
|
|
13
18
|
def clear_output(wait=false)
|
|
14
19
|
Display.clear_output(wait)
|
|
15
20
|
end
|
|
16
21
|
|
|
22
|
+
# Format the given object into HTML table
|
|
17
23
|
def table(s, **options)
|
|
18
|
-
html(HTML.table(s, options))
|
|
24
|
+
html(HTML.table(s, **options))
|
|
19
25
|
end
|
|
20
26
|
|
|
27
|
+
# Treat the given string as LaTeX text
|
|
21
28
|
def latex(s)
|
|
22
29
|
convert(s, mime: 'text/latex')
|
|
23
30
|
end
|
|
24
31
|
alias tex latex
|
|
25
32
|
|
|
33
|
+
# Format the given string of TeX equation into LaTeX text
|
|
26
34
|
def math(s)
|
|
27
35
|
convert("$$#{s}$$", mime: 'text/latex')
|
|
28
36
|
end
|
|
29
37
|
|
|
38
|
+
# Treat the given string as HTML
|
|
30
39
|
def html(s)
|
|
31
40
|
convert(s, mime: 'text/html')
|
|
32
41
|
end
|
|
33
42
|
|
|
43
|
+
# Treat the given string as JavaScript code
|
|
34
44
|
def javascript(s)
|
|
35
45
|
convert(s, mime: 'application/javascript')
|
|
36
46
|
end
|
|
37
47
|
|
|
48
|
+
# Treat the given string as SVG text
|
|
38
49
|
def svg(s)
|
|
39
50
|
convert(s, mime: 'image/svg+xml')
|
|
40
51
|
end
|