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.
@@ -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
- class<< self
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
- def initialize(config_file)
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 = create_backend
53
+ @backend = PlainBackend.new
25
54
  @running = true
55
+
56
+ self.class.events.trigger(:initialized, self)
26
57
  end
27
58
 
28
- def create_backend
29
- PryBackend.new
30
- rescue Exception => e
31
- IRuby.logger.warn "Could not load PryBackend: #{e.message}\n#{e.backtrace.join("\n")}" unless LoadError === e
32
- PlainBackend.new
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
- @execution_count += 1 if msg[:content]['store_history']
87
- @session.send(:publish, :execute_input, code: code, execution_count: @execution_count)
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, msg[:content]['store_history'])
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
@@ -46,6 +46,11 @@ module IRuby
46
46
  lines.each { |s| write(s) }
47
47
  end
48
48
 
49
+ # Called by irb
50
+ def set_encoding(extern, intern)
51
+ a = extern
52
+ end
53
+
49
54
  private
50
55
 
51
56
  def build_string
data/lib/iruby/session.rb CHANGED
@@ -10,6 +10,7 @@ module IRuby
10
10
  def initialize(config, adapter_name=nil)
11
11
  @config = config
12
12
  @adapter = create_session_adapter(config, adapter_name)
13
+ @last_recvd_msg = nil
13
14
 
14
15
  setup
15
16
  setup_sockets
@@ -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