Class: IRuby::Kernel

Inherits:
Object
  • Object
show all
Defined in:
lib/iruby/kernel.rb

Constant Summary collapse

RED =
"\e[31m"
RESET =
"\e[0m"
EVENTS =
[
  :pre_execute,
  :pre_run_cell,
  :post_run_cell,
  :post_execute
].freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_file, session_adapter_name = nil) ⇒ Kernel

Returns a new instance of Kernel.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/iruby/kernel.rb', line 40

def initialize(config_file, session_adapter_name=nil)
  @config = MultiJson.load(File.read(config_file))
  IRuby.logger.debug("IRuby kernel start with config #{@config}")
  Kernel.instance = self

  @session = Session.new(@config, session_adapter_name)
  $stdout = OStream.new(@session, :stdout)
  $stderr = OStream.new(@session, :stderr)

  init_parent_process_poller

  @events = EventManager.new(EVENTS)
  @execution_count = 0
  @backend = PlainBackend.new
  @running = true

  self.class.events.trigger(:initialized, self)
end

Class Attribute Details

.eventsObject (readonly)

Return the event manager defined in the ‘IRuby::Kernel` class. This event manager can handle the following event:

  • ‘initialized`: The event occurred after the initialization of a `IRuby::Kernel` instance is finished

Examples:

Registering initialized event

IRuby::Kernel.events.register(:initialized) do |result|
  STDERR.puts "IRuby kernel has been initialized"
end

See Also:



24
25
26
# File 'lib/iruby/kernel.rb', line 24

def events
  @events
end

.instanceObject

Returns the singleton kernel instance



27
28
29
# File 'lib/iruby/kernel.rb', line 27

def instance
  @instance
end

Instance Attribute Details

#eventsObject (readonly)

Returns the event manager defined in a ‘IRuby::Kernel` instance. This event manager can handle the following events:

  • ‘pre_execute`: The event occurred before running the code

  • ‘pre_run_cell`: The event occurred before running the code and if the code execution is not silent

  • ‘post_execute`: The event occurred after running the code

  • ‘post_run_cell`: The event occurred after running the code and if the code execution is not silent

The callback functions of ‘pre_run_cell` event must take one argument to get an `ExecutionInfo` object. The callback functions of `post_run_cell` event must take one argument to get the result of the code execution.

Examples:

Registering post_run_cell event

IRuby::Kernel.instance.events.register(:post_run_cell) do |result|
  STDERR.puts "The result of the last execution: %p" % result
end

See Also:



85
86
87
# File 'lib/iruby/kernel.rb', line 85

def events
  @events
end

#sessionObject (readonly)

Returns a session object



31
32
33
# File 'lib/iruby/kernel.rb', line 31

def session
  @session
end

Instance Method Details

#comm_close(msg) ⇒ Object



297
298
299
300
301
# File 'lib/iruby/kernel.rb', line 297

def comm_close(msg)
  comm_id = msg[:content]['comm_id']
  Comm.comm[comm_id].handle_close(msg[:content]['data'])
  Comm.comm.delete(comm_id)
end

#comm_msg(msg) ⇒ Object



292
293
294
# File 'lib/iruby/kernel.rb', line 292

def comm_msg(msg)
  Comm.comm[msg[:content]['comm_id']].handle_msg(msg[:content]['data'])
end

#comm_open(msg) ⇒ Object



285
286
287
288
289
# File 'lib/iruby/kernel.rb', line 285

def comm_open(msg)
  comm_id = msg[:content]['comm_id']
  target_name = msg[:content]['target_name']
  Comm.comm[comm_id] = Comm.target[target_name].new(target_name, comm_id)
end

#complete_request(msg) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/iruby/kernel.rb', line 245

def complete_request(msg)
  # HACK for #26, only complete last line
  code = msg[:content]['code']
  if start = code.rindex(/\s|\R/)
    code = code[start+1..-1]
    start += 1
  end
  @session.send(:reply, :complete_reply,
                matches: @backend.complete(code),
                cursor_start: start.to_i,
                cursor_end: msg[:content]['cursor_pos'],
                metadata: {},
                status: :ok)
end

#connect_request(msg) ⇒ Object



261
262
263
# File 'lib/iruby/kernel.rb', line 261

def connect_request(msg)
  @session.send(:reply, :connect_reply, Hash[%w(shell_port iopub_port stdin_port hb_port).map {|k| [k, @config[k]] }])
end

#dispatchObject



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/iruby/kernel.rb', line 132

def dispatch
  msg = @session.recv(:reply)
  IRuby.logger.debug "Kernel#dispatch: msg = #{msg}"
  type = msg[:header]['msg_type']
  raise "Unknown message type: #{msg.inspect}" unless type =~ /comm_|_request\Z/ && respond_to?(type)
  begin
    send_status :busy
    send(type, msg)
  ensure
    send_status :idle
  end
rescue Exception => e
  IRuby.logger.debug "Kernel error: #{e.message}\n#{e.backtrace.join("\n")}"
  @session.send(:publish, :error, error_content(e))
end

#error_content(e) ⇒ Object



229
230
231
232
233
234
235
# File 'lib/iruby/kernel.rb', line 229

def error_content(e)
  rindex = e.backtrace.rindex{|line| line.start_with?(@backend.eval_path)} || -1
  backtrace = SyntaxError === e  && rindex == -1 ? [] : e.backtrace[0..rindex]
  { ename: e.class.to_s,
    evalue: e.message,
    traceback: ["#{RED}#{e.class}#{RESET}: #{e.message}", *backtrace] }
end

#execute_request(msg) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/iruby/kernel.rb', line 177

def execute_request(msg)
  code = msg[:content]['code']
  silent = msg[:content]['silent']
  # https://jupyter-client.readthedocs.io/en/stable/messaging.html#execute
  store_history = silent ? false : msg[:content].fetch('store_history', true)

  @execution_count += 1 if store_history

  unless silent
    @session.send(:publish, :execute_input, code: code, execution_count: @execution_count)
  end

  events.trigger(:pre_execute)
  unless silent
    exec_info = ExecutionInfo.new(code, store_history, silent)
    events.trigger(:pre_run_cell, exec_info)
  end

  content = {
    status: :ok,
    payload: [],
    user_expressions: {},
    execution_count: @execution_count
  }

  result = nil
  begin
    result = @backend.eval(code, store_history)
  rescue SystemExit
    content[:payload] << { source: :ask_exit }
  rescue Exception => e
    content = error_content(e)
    @session.send(:publish, :error, content)
    content[:status] = :error
    content[:execution_count] = @execution_count
  end

  unless result.nil? || silent
    @session.send(:publish, :execute_result,
                  data: Display.display(result),
                  metadata: {},
                  execution_count: @execution_count)
  end

  events.trigger(:post_execute)
  # **{} is for Ruby2.7. Gnuplot#to_hash returns an Array.
  events.trigger(:post_run_cell, result, **{}) unless silent

  @session.send(:reply, :execute_reply, content)
end

#history_request(msg) ⇒ Object



272
273
274
275
276
# File 'lib/iruby/kernel.rb', line 272

def history_request(msg)
  # we will just send back empty history for now, pending clarification
  # as requested in ipython/ipython#3806
  @session.send(:reply, :history_reply, history: [])
end

#inspect_request(msg) ⇒ Object



279
280
281
282
# File 'lib/iruby/kernel.rb', line 279

def inspect_request(msg)
  # not yet implemented. See (#119).
  @session.send(:reply, :inspect_reply, status: :ok, found: false, data: {}, metadata: {})
end

#is_complete_request(msg) ⇒ Object



238
239
240
241
242
# File 'lib/iruby/kernel.rb', line 238

def is_complete_request(msg)
  # FIXME: the code completeness should be judged by using ripper or other Ruby parser
  @session.send(:reply, :is_complete_reply,
                status: :unknown)
end

#kernel_info_request(msg) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/iruby/kernel.rb', line 149

def kernel_info_request(msg)
  @session.send(:reply, :kernel_info_reply,
                protocol_version: '5.0',
                implementation: 'iruby',
                implementation_version: IRuby::VERSION,
                language_info: {
                  name: 'ruby',
                  version: RUBY_VERSION,
                  mimetype: 'application/x-ruby',
                  file_extension: '.rb'
                },
                banner: "IRuby #{IRuby::VERSION} (with #{@session.description})",
                help_links: [
                  {
                    text: "Ruby Documentation",
                    url:  "https://ruby-doc.org/"
                  }
                ],
                status: :ok)
end

#runObject



124
125
126
127
128
129
# File 'lib/iruby/kernel.rb', line 124

def run
  send_status :starting
  while @running
    dispatch
  end
end

#send_status(status) ⇒ Object



171
172
173
174
# File 'lib/iruby/kernel.rb', line 171

def send_status(status)
  IRuby.logger.debug "Send status: #{status}"
  @session.send(:publish, :status, execution_state: status)
end

#shutdown_request(msg) ⇒ Object



266
267
268
269
# File 'lib/iruby/kernel.rb', line 266

def shutdown_request(msg)
  @session.send(:reply, :shutdown_reply, msg[:content])
  @running = false
end

#switch_backend!(backend) ⇒ true, false

Switch the backend (interactive shell) system

Parameters:

  • backend (:irb, :plain, :pry)

    Specify the backend name switched to

Returns:

  • (true, false)

    true if the switching succeeds, otherwise false



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/iruby/kernel.rb', line 92

def switch_backend!(backend)
  name = case backend
         when String, Symbol
           name = backend.downcase
         else
           name = backend
         end

  backend_class = case name
                  when :irb, :plain
                    PlainBackend
                  when :pry
                    PryBackend
                  else
                    raise ArgumentError,
                      "Unknown backend name: %p" % backend
                  end

  begin
    new_backend = backend_class.new
    @backend = new_backend
    true
  rescue Exception => e
    unless LoadError === e
      IRuby.logger.warn "Could not load #{backend_class}: " +
                        "#{e.message}\n#{e.backtrace.join("\n")}"
    end
    return false
  end
end