Skip to content

Commit 9ebae7d

Browse files
authored
Merge pull request #304 from SciRuby/to_iruby_mimebundle
Add to_iruby_mimebundle support
2 parents 1205fa1 + d0c8e9c commit 9ebae7d

File tree

3 files changed

+325
-39
lines changed

3 files changed

+325
-39
lines changed

.github/workflows/ubuntu.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jobs:
4141

4242
- name: Install requirements on ubuntu
4343
run: |
44+
sudo apt update
4445
sudo apt install -y --no-install-recommends \
4546
libczmq-dev \
4647
python3 \

lib/iruby/display.rb

Lines changed: 136 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
module IRuby
44
module Display
5+
DEFAULT_MIME_TYPE_FORMAT_METHODS = {
6+
"text/html" => :to_html,
7+
"text/markdown" => :to_markdown,
8+
"image/svg+xml" => :to_svg,
9+
"image/png" => :to_png,
10+
"appliation/pdf" => :to_pdf,
11+
"image/jpeg" => :to_jpeg,
12+
"text/latex" => [:to_latex, :to_tex],
13+
# NOTE: Do not include the entry of "application/json" because
14+
# all objects can respond to `to_json` due to json library
15+
# "application/json" => :to_json,
16+
"application/javascript" => :to_javascript,
17+
nil => :to_iruby,
18+
"text/plain" => :inspect
19+
}.freeze
20+
521
class << self
622
# @private
723
def convert(obj, options)
@@ -24,17 +40,38 @@ def display(obj, options = {})
2440
raise 'Invalid mime type' unless exact_mime.include?('/')
2541
end
2642

27-
data = {}
43+
data = render_mimebundle(obj, exact_mime, fuzzy_mime)
44+
45+
# Render by additional formatters
46+
render_by_registry(data, obj, exact_mime, fuzzy_mime)
47+
48+
# Render by to_xxx methods
49+
DEFAULT_MIME_TYPE_FORMAT_METHODS.each do |mime, methods|
50+
next if mime.nil? && !data.empty? # for to_iruby
2851

29-
# Render additional representation
30-
render(data, obj, exact_mime, fuzzy_mime)
52+
next if mime && data.key?(mime) # do not overwrite
3153

32-
# IPython always requires a text representation
33-
render(data, obj, 'text/plain', nil) unless data['text/plain']
54+
method = Array(methods).find {|m| obj.respond_to?(m) }
55+
next if method.nil?
56+
57+
result = obj.send(method)
58+
case mime
59+
when nil # to_iruby
60+
case result
61+
when Array
62+
mime, result = result
63+
else
64+
warn(("Ignore the result of to_iruby method of %p because " +
65+
"it does not return a pair of mime-type and formatted representation") % obj)
66+
next
67+
end
68+
end
69+
data[mime] = result
70+
end
3471

3572
# As a last resort, interpret string representation of the object
3673
# as the given mime type.
37-
if exact_mime && data.none? { |m, _| exact_mime == m }
74+
if exact_mime && !data.key?(exact_mime)
3875
data[exact_mime] = protect(exact_mime, obj)
3976
end
4077

@@ -67,7 +104,21 @@ def ascii?(mime)
67104
end
68105
end
69106

70-
def render(data, obj, exact_mime, fuzzy_mime)
107+
private def render_mimebundle(obj, exact_mime, fuzzy_mime)
108+
data = {}
109+
if obj.respond_to?(:to_iruby_mimebundle)
110+
include_mime = [exact_mime].compact
111+
formats, metadata = obj.to_iruby_mimebundle(include: include_mime)
112+
formats.each do |mime, value|
113+
if fuzzy_mime.nil? || mime.include?(fuzzy_mime)
114+
data[mime] = value
115+
end
116+
end
117+
end
118+
data
119+
end
120+
121+
private def render_by_registry(data, obj, exact_mime, fuzzy_mime)
71122
# Filter matching renderer by object type
72123
renderer = Registry.renderer.select { |r| r.match?(obj) }
73124

@@ -88,6 +139,8 @@ def render(data, obj, exact_mime, fuzzy_mime)
88139
# Return first render result which has the right mime type
89140
renderer.each do |r|
90141
mime, result = r.render(obj)
142+
next if data.key?(mime)
143+
91144
if mime && result && (!exact_mime || exact_mime == mime) && (!fuzzy_mime || mime.include?(fuzzy_mime))
92145
data[mime] = protect(mime, result)
93146
break
@@ -98,6 +151,19 @@ def render(data, obj, exact_mime, fuzzy_mime)
98151
end
99152
end
100153

154+
private def render_by_to_iruby(data, obj)
155+
if obj.respond_to?(:to_iruby)
156+
result = obj.to_iruby
157+
mime, rep = case result
158+
when Array
159+
result
160+
else
161+
[nil, result]
162+
end
163+
data[mime] = rep
164+
end
165+
end
166+
101167
class Representation
102168
attr_reader :object, :options
103169

@@ -120,6 +186,60 @@ def new(obj, options)
120186
end
121187
end
122188

189+
class FormatMatcher
190+
def initialize(&block)
191+
@block = block
192+
end
193+
194+
def call(obj)
195+
@block.(obj)
196+
end
197+
198+
def inspect
199+
"#{self.class.name}[%p]" % @block
200+
end
201+
end
202+
203+
class RespondToFormatMatcher < FormatMatcher
204+
def initialize(name)
205+
super() {|obj| obj.respond_to?(name) }
206+
@name = name
207+
end
208+
209+
attr_reader :name
210+
211+
def inspect
212+
"#{self.class.name}[respond_to?(%p)]" % name
213+
end
214+
end
215+
216+
class TypeFormatMatcher < FormatMatcher
217+
def initialize(class_block)
218+
super() do |obj|
219+
begin
220+
self.klass === obj
221+
# We have to rescue all exceptions since constant autoloading could fail with a different error
222+
rescue Exception
223+
false
224+
end
225+
end
226+
@class_block = class_block
227+
end
228+
229+
def klass
230+
@class_block.()
231+
end
232+
233+
def inspect
234+
klass = begin
235+
@class_block.()
236+
rescue Exception
237+
@class_block
238+
end
239+
"#{self.class.name}[%p]" % klass
240+
end
241+
end
242+
123243
class Renderer
124244
attr_reader :match, :mime, :priority
125245

@@ -159,25 +279,21 @@ def renderer
159279
]
160280

161281
def match(&block)
162-
@match = block
282+
@match = FormatMatcher.new(&block)
163283
priority 0
164284
nil
165285
end
166286

167287
def respond_to(name)
168-
match { |obj| obj.respond_to?(name) }
288+
@match = RespondToFormatMatcher.new(name)
289+
priority 0
290+
nil
169291
end
170292

171293
def type(&block)
172-
match do |obj|
173-
begin
174-
block.call === obj
175-
# We have to rescue all exceptions since constant autoloading could fail with a different error
176-
rescue Exception
177-
rescue #NameError
178-
false
179-
end
180-
end
294+
@match = TypeFormatMatcher.new(block)
295+
priority 0
296+
nil
181297
end
182298

183299
def priority(p)
@@ -301,36 +417,17 @@ def format(mime = nil, &block)
301417
type { Gruff::Base }
302418
format 'image/png', &:to_blob
303419

304-
respond_to :to_html
305-
format 'text/html', &:to_html
306-
307-
respond_to :to_latex
308-
format 'text/latex', &:to_latex
309-
310-
respond_to :to_tex
311-
format 'text/latex', &:to_tex
312-
313-
respond_to :to_javascript
314-
format 'text/javascript', &:to_javascript
315-
316-
respond_to :to_svg
420+
type { Rubyvis::Mark }
317421
format 'image/svg+xml' do |obj|
318-
obj.render if defined?(Rubyvis) && Rubyvis::Mark === obj
422+
obj.render
319423
obj.to_svg
320424
end
321425

322-
respond_to :to_iruby
323-
format(&:to_iruby)
324-
325426
match { |obj| obj.respond_to?(:path) && obj.method(:path).arity == 0 && File.readable?(obj.path) }
326427
format do |obj|
327428
mime = MIME::Types.of(obj.path).first.to_s
328429
[mime, File.read(obj.path)] if SUPPORTED_MIMES.include?(mime)
329430
end
330-
331-
type { Object }
332-
priority(-1000)
333-
format 'text/plain', &:inspect
334431
end
335432
end
336433
end

0 commit comments

Comments
 (0)