-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathiruby.rb
262 lines (224 loc) · 8.62 KB
/
iruby.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
require 'matplotlib'
module Matplotlib
module IRuby
module HookExtension
def self.extended(obj)
@event_registry ||= {}
@event_registry[obj] = {}
end
def self.register_event(target, event, hook)
@event_registry[target][event] ||= []
@event_registry[target][event] << hook
end
def register_event(event, hook=nil, &block)
HookExtension.register_event(self, event, [hook, block].compact)
end
def self.unregister_event(target, event, hook)
return unless @event_registry[target]
return unless @event_registry[target][event]
@event_registry[target][event].delete(hook)
end
def unregister_event(event, hook)
HookExtension.unregister_event(self, event, hook)
end
def self.trigger_event(target, event)
return unless @event_registry[target][event]
@event_registry[target][event].each do |hooks|
hooks.to_a.each do |hook|
hook.call if hook
end
end
rescue Exception
$stderr.puts "Error occurred in triggerred event: target=#{target} event=#{event}", $!.to_s, *$!.backtrace
end
def trigger_event(event)
HookExtension.trigger_event(self, event)
end
def execute_request(msg)
code = msg[:content]['code']
@execution_count += 1 if msg[:content]['store_history']
@session.send(:publish, :execute_input, code: code, execution_count: @execution_count)
trigger_event(:pre_execute)
content = {
status: :ok,
payload: [],
user_expressions: {},
execution_count: @execution_count
}
result = nil
begin
result = @backend.eval(code, msg[:content]['store_history'])
rescue SystemExit
content[:payload] << { source: :ask_exit }
rescue Exception => e
content = error_content(e)
@session.send(:publish, :error, content)
end
trigger_event(:post_execute)
@session.send(:reply, :execute_reply, content)
@session.send(:publish, :execute_result,
data: ::IRuby::Display.display(result),
metadata: {},
execution_count: @execution_count) unless result.nil? || msg[:content]['silent']
end
end
AGG_FORMATS = {
"image/png" => "png",
"application/pdf" => "pdf",
"application/eps" => "eps",
"image/eps" => "eps",
"application/postscript" => "ps",
"image/svg+xml" => "svg"
}.freeze
module Helper
BytesIO = PyCall.import_module('io').BytesIO
def register_formats
type { Figure }
AGG_FORMATS.each do |mime, format|
format mime do |fig|
unless fig.canvas.get_supported_filetypes.has_key?(format)
raise Error, "Unable to display a figure in #{format} format"
end
io = BytesIO.new
fig.canvas.print_figure(io, format: format, bbox_inches: 'tight')
io.getvalue
end
end
end
end
class << self
# NOTE: This method is translated from `IPython.core.activate_matplotlib` function.
def activate(gui=:inline)
enable_matplotlib(gui)
end
GUI_BACKEND_MAP = {
tk: :TkAgg,
gtk: :GTKAgg,
gtk3: :GTK3Agg,
wx: :WXAgg,
qt: :Qt4Agg,
qt4: :Qt4Agg,
qt5: :Qt5Agg,
osx: :MacOSX,
nbagg: :nbAgg,
notebook: :nbAgg,
agg: :agg,
inline: 'module://matplotlib_rb.backend_inline',
}.freeze
BACKEND_GUI_MAP = Hash[GUI_BACKEND_MAP.select {|k, v| v }].freeze
private_constant :GUI_BACKEND_MAP, :BACKEND_GUI_MAP
def available_gui_names
GUI_BACKEND_MAP.keys
end
private
# This method is based on IPython.core.interactiveshell.InteractiveShell.enable_matplotlib function.
def enable_matplotlib(gui=nil)
gui, backend = find_gui_and_backend(gui, @gui_select)
if gui != :inline
if @gui_select.nil?
@gui_select = gui
elsif gui != @gui_select
$stderr.puts "Warning: Cannot change to a different GUI toolkit: #{gui}. Using #{@gui_select} instead."
gui, backend = find_gui_and_backend(@gui_select)
end
end
activate_matplotlib(backend)
configure_inline_support(backend)
# self.enable_gui(gui)
# register matplotlib-aware execution runner for ExecutionMagics
[gui, backend]
end
# Given a gui string return the gui and matplotlib backend.
# This method is based on IPython.core.pylabtools.find_gui_and_backend function.
#
# @param [String, Symbol, nil] gui can be one of (:tk, :gtk, :wx, :qt, :qt4, :inline, :agg).
# @param [String, Symbol, nil] gui_select can be one of (:tk, :gtk, :wx, :qt, :qt4, :inline, :agg).
#
# @return A pair of (gui, backend) where backend is one of (:TkAgg, :GTKAgg, :WXAgg, :Qt4Agg, :agg).
def find_gui_and_backend(gui=nil, gui_select=nil)
gui = gui.to_sym if gui.kind_of? String
if gui && gui != :auto
# select backend based on requested gui
backend = GUI_BACKEND_MAP[gui]
gui = nil if gui == :agg
return [gui, backend]
end
backend = Matplotlib.rcParamsOrig['backend']&.to_sym
gui = BACKEND_GUI_MAP[backend]
# If we have already had a gui active, we need it and inline are the ones allowed.
if gui_select && gui != gui_select
gui = gui_select
backend = backend[gui]
end
[gui, backend]
end
# Activate the given backend and set interactive to true.
# This method is based on IPython.core.pylabtools.activate_matplotlib function.
#
# @param [String, Symbol] backend a name of matplotlib backend
def activate_matplotlib(backend)
require 'matplotlib'
Matplotlib.interactive(true)
backend = backend.to_s
Matplotlib.rcParams['backend'] = backend
require 'matplotlib/pyplot'
Matplotlib::Pyplot.switch_backend(backend)
# TODO: should support wrapping python function
# plt = Matplotlib::Pyplot
# plt.__pyobj__.show._needmain = false
# plt.__pyobj__.draw_if_interactive = flag_calls(plt.__pyobj__.draw_if_interactive)
end
# This method is based on IPython.core.pylabtools.configure_inline_support function.
#
# @param shell an instance of IRuby shell
# @param backend a name of matplotlib backend
def configure_inline_support(backend)
# Temporally monky-patching IRuby kernel to enable flushing and closing figures.
# TODO: Make this feature a pull-request for sciruby/iruby.
kernel = ::IRuby::Kernel.instance
kernel.extend HookExtension
if backend == GUI_BACKEND_MAP[:inline]
kernel.register_event(:post_execute, method(:flush_figures))
# TODO: save original rcParams and overwrite rcParams with IRuby-specific configuration
new_backend_name = :inline
else
kernel.unregister_event(:post_execute, method(:flush_figures))
# TODO: restore saved original rcParams
new_backend_name = :not_inline
end
if new_backend_name != @current_backend
# TODO: select figure formats
@current_backend = new_backend_name
end
end
# This method is based on ipykernel.pylab.backend_inline.flush_figures function.
def flush_figures
# TODO: I want to allow users to turn on/off automatic figure closing.
show_figures(true)
end
# This method is based on ipykernel.pylab.backend_inline.show function.
#
# @param [true, false] close If true, a `plt.close('all')` call is automatically issued after sending all the figures.
def show_figures(close=false)
_pylab_helpers = PyCall.import_module('matplotlib._pylab_helpers')
gcf = _pylab_helpers.Gcf
kernel = ::IRuby::Kernel.instance
gcf.get_all_fig_managers.each do |fig_manager|
data = ::IRuby::Display.display(fig_manager.canvas.figure)
kernel.session.send(:publish, :execute_result,
data: data,
metadata: {},
execution_count: kernel.instance_variable_get(:@execution_count))
end
ensure
unless gcf.get_all_fig_managers.length == 0
Matplotlib::Pyplot.close('all')
end
end
end
end
end
::IRuby::Display::Registry.module_eval do
extend Matplotlib::IRuby::Helper
register_formats
end