Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:

- name: Install requirements on ubuntu
run: |
sudo apt update
sudo apt install -y --no-install-recommends \
libczmq-dev \
python3 \
Expand Down
175 changes: 136 additions & 39 deletions lib/iruby/display.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

module IRuby
module Display
DEFAULT_MIME_TYPE_FORMAT_METHODS = {
"text/html" => :to_html,
"text/markdown" => :to_markdown,
"image/svg+xml" => :to_svg,
"image/png" => :to_png,
"appliation/pdf" => :to_pdf,
"image/jpeg" => :to_jpeg,
"text/latex" => [:to_latex, :to_tex],
# NOTE: Do not include the entry of "application/json" because
# all objects can respond to `to_json` due to json library
# "application/json" => :to_json,
"application/javascript" => :to_javascript,
nil => :to_iruby,
"text/plain" => :inspect
}.freeze

class << self
# @private
def convert(obj, options)
Expand All @@ -24,17 +40,38 @@ def display(obj, options = {})
raise 'Invalid mime type' unless exact_mime.include?('/')
end

data = {}
data = render_mimebundle(obj, exact_mime, fuzzy_mime)

# Render by additional formatters
render_by_registry(data, obj, exact_mime, fuzzy_mime)

# Render by to_xxx methods
DEFAULT_MIME_TYPE_FORMAT_METHODS.each do |mime, methods|
next if mime.nil? && !data.empty? # for to_iruby

# Render additional representation
render(data, obj, exact_mime, fuzzy_mime)
next if mime && data.key?(mime) # do not overwrite

# IPython always requires a text representation
render(data, obj, 'text/plain', nil) unless data['text/plain']
method = Array(methods).find {|m| obj.respond_to?(m) }
next if method.nil?

result = obj.send(method)
case mime
when nil # to_iruby
case result
when Array
mime, result = result
else
warn(("Ignore the result of to_iruby method of %p because " +
"it does not return a pair of mime-type and formatted representation") % obj)
next
end
end
data[mime] = result
end

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

Expand Down Expand Up @@ -67,7 +104,21 @@ def ascii?(mime)
end
end

def render(data, obj, exact_mime, fuzzy_mime)
private def render_mimebundle(obj, exact_mime, fuzzy_mime)
data = {}
if obj.respond_to?(:to_iruby_mimebundle)
include_mime = [exact_mime].compact
formats, metadata = obj.to_iruby_mimebundle(include: include_mime)
formats.each do |mime, value|
if fuzzy_mime.nil? || mime.include?(fuzzy_mime)
data[mime] = value
end
end
end
data
end

private def render_by_registry(data, obj, exact_mime, fuzzy_mime)
# Filter matching renderer by object type
renderer = Registry.renderer.select { |r| r.match?(obj) }

Expand All @@ -88,6 +139,8 @@ def render(data, obj, exact_mime, fuzzy_mime)
# Return first render result which has the right mime type
renderer.each do |r|
mime, result = r.render(obj)
next if data.key?(mime)

if mime && result && (!exact_mime || exact_mime == mime) && (!fuzzy_mime || mime.include?(fuzzy_mime))
data[mime] = protect(mime, result)
break
Expand All @@ -98,6 +151,19 @@ def render(data, obj, exact_mime, fuzzy_mime)
end
end

private def render_by_to_iruby(data, obj)
if obj.respond_to?(:to_iruby)
result = obj.to_iruby
mime, rep = case result
when Array
result
else
[nil, result]
end
data[mime] = rep
end
end

class Representation
attr_reader :object, :options

Expand All @@ -120,6 +186,60 @@ def new(obj, options)
end
end

class FormatMatcher
def initialize(&block)
@block = block
end

def call(obj)
@block.(obj)
end

def inspect
"#{self.class.name}[%p]" % @block
end
end

class RespondToFormatMatcher < FormatMatcher
def initialize(name)
super() {|obj| obj.respond_to?(name) }
@name = name
end

attr_reader :name

def inspect
"#{self.class.name}[respond_to?(%p)]" % name
end
end

class TypeFormatMatcher < FormatMatcher
def initialize(class_block)
super() do |obj|
begin
self.klass === obj
# We have to rescue all exceptions since constant autoloading could fail with a different error
rescue Exception
false
end
end
@class_block = class_block
end

def klass
@class_block.()
end

def inspect
klass = begin
@class_block.()
rescue Exception
@class_block
end
"#{self.class.name}[%p]" % klass
end
end

class Renderer
attr_reader :match, :mime, :priority

Expand Down Expand Up @@ -159,25 +279,21 @@ def renderer
]

def match(&block)
@match = block
@match = FormatMatcher.new(&block)
priority 0
nil
end

def respond_to(name)
match { |obj| obj.respond_to?(name) }
@match = RespondToFormatMatcher.new(name)
priority 0
nil
end

def type(&block)
match do |obj|
begin
block.call === obj
# We have to rescue all exceptions since constant autoloading could fail with a different error
rescue Exception
rescue #NameError
false
end
end
@match = TypeFormatMatcher.new(block)
priority 0
nil
end

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

respond_to :to_html
format 'text/html', &:to_html

respond_to :to_latex
format 'text/latex', &:to_latex

respond_to :to_tex
format 'text/latex', &:to_tex

respond_to :to_javascript
format 'text/javascript', &:to_javascript

respond_to :to_svg
type { Rubyvis::Mark }
format 'image/svg+xml' do |obj|
obj.render if defined?(Rubyvis) && Rubyvis::Mark === obj
obj.render
obj.to_svg
end

respond_to :to_iruby
format(&:to_iruby)

match { |obj| obj.respond_to?(:path) && obj.method(:path).arity == 0 && File.readable?(obj.path) }
format do |obj|
mime = MIME::Types.of(obj.path).first.to_s
[mime, File.read(obj.path)] if SUPPORTED_MIMES.include?(mime)
end

type { Object }
priority(-1000)
format 'text/plain', &:inspect
end
end
end
Loading