How to get a markdown repr of an object? (MIME type fallbacks)

repr takes a mimetype arguement.
Sometimes, it is evan used.
e.g.

julia> repr("text/html",Docs.html"<b>foo</b>")
"<b>foo</b>"

julia> repr("text/markdown", "foo")
"foo"

Mostly though it is not:

julia> repr("text/markdown", 1)
ERROR: MethodError: no method matching show(::Base.GenericIOBuffer{Array{UInt8,1}}, ::MIME{Symbol("text/markdown")}, ::Int64)
Closest candidates are:
  show(::IO, ::MIME{Symbol("text/plain")}, ::Any) at sysimg.jl:194
  show(::IO, ::MIME{Symbol("text/markdown")}, ::Markdown.MD) at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.1/Markdown/src/render/plain.jl:140
  show(::IO, ::MIME{Symbol("text/csv")}, ::Any) at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v1.1/DelimitedFiles/src/DelimitedFiles.jl:828
  ...
Stacktrace:
 [1] __binrepr(::MIME{Symbol("text/markdown")}, ::Int64, ::Nothing) at ./multimedia.jl:127
 [2] _textrepr(::MIME{Symbol("text/markdown")}, ::Int64, ::Nothing) at ./multimedia.jl:119
 [3] #repr#1(::Nothing, ::Function, ::MIME{Symbol("text/markdown")}, ::Int64) at ./multimedia.jl:115
 [4] (::getfield(Base, Symbol("#kw##repr")))(::NamedTuple{(:context,),Tuple{Nothing}}, ::typeof(repr), ::MIME{Symbol("text/markdown")}, ::Int64) at ./none:0
 [5] #repr#2(::Nothing, ::Function, ::String, ::Int64) at ./multimedia.jl:116
 [6] repr(::String, ::Int64) at ./multimedia.jl:116
 [7] top-level scope at none:0

Which makes sense when there is no canonical markdown representation of the thing.
However, markdown is a pretty flexible format.
So I would like to fallback, first to HTML, then to plain text,
then to just what ever repr(x) returns.

Is there any better way than doing exception handling?

"""
    markdown_repr(x)

Returns the most suitable method for displaying `x` in a markdown document.
"""
function markdown_repr(x)
    for mime_type in ("text/markdown", "text/html", "text/plain")
        try
            return repr(mime_type, x)
        catch err
            err isa MethodError || rethrow(err)
        end
    end
    return repr(x)
end

I believe showable is meant for cases like this, but it is not perfect since it just checks that the method exist for the given argument and mime type. It will for example fail when someone defines a fallback like

function Base.show(io::IO, ::MIME, x::MyType)
    error("MyType does not support this MIME type")
end

see e.g. https://github.com/JuliaLang/IJulia.jl/issues/835.

1 Like

if showable is good enough for IJulia
(which indeed it does seem to be https://github.com/JuliaLang/IJulia.jl/blob/master/src/display.jl#L4)
then it is good enough for me.

IJulia also wraps the whole thing in a try-catch
Which seems reasonable.
But at least showable lets us not try to do things only to get MethodErrors.

"""
    markdown_repr(x)

Returns the most suitable method for displaying `x` in a markdown document.
"""
function markdown_repr(x)
    for mime_type in ("text/markdown", "text/html", "text/plain")
        try
            if showable(mime_type, x)
                repr(mime_type, x)
            end
        catch
            @debug "It said it was showable, but it lied" mime_type x exception=err
            
            # we blindly catch everything, because who knows what evil
            # people are doing in their misimplemented `repr` functions
        end
    end
    return repr(x)
end

That’s to handle objects with buggy show functions, where you’d still like to see text/plain output if nothing else. You can omit this if you’re not handling arbitrary user interactions and don’t mind throwing an exception if there are bugs.

Types that define show via generic ::MIME handlers should also override showable to indicate what types they can actually handle. For example, PyCall does this because whether a rich representation exists for a given Python object can only be determined at runtime.

showable("text/markdown", "Hello **World**")

returns false
It should return true since

repr("text/markdown", "Hello **World**")

returns “Hello **World**” and does not cause any error
Is this a bug or am I doing something wrong?

showable tells you whether your object can be shown, not represented:

julia> showable("text/markdown", "Hello **World**")
false

julia> show(stdout, "text/markdown", "Hello **World**")
ERROR: MethodError: no method matching show(::Base.TTY, ::MIME{Symbol("text/markdown")}, ::String)
Closest candidates are:
  show(::IO, ::MIME{Symbol("text/plain")}, ::Any) at sysimg.jl:194
  show(::IO, ::MIME{Symbol("text/markdown")}, ::Markdown.MD) at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.0\Markdown\src\render\plain.jl:140
  show(::IO, ::MIME{Symbol("text/csv")}, ::Any) at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.0\DelimitedFiles\src\DelimitedFiles.jl:828
  ...
Stacktrace:
 [1] show(::Base.TTY, ::String, ::String) at .\multimedia.jl:79
 [2] top-level scope at none:0

julia> showable("text/markdown", Markdown.parse("Hello **World**"))
true

julia> show(stdout, "text/markdown", Markdown.parse("Hello **World**"))
Hello **World**
2 Likes