https://github.com/JuliaLang/julia
Tip revision: 090c4c98576bc8764e1860bb605ffd8a574f3fd9 authored by Jeff Bezanson on 26 February 2021, 19:57:38 UTC
simplify and improve printing of qualified names
simplify and improve printing of qualified names
Tip revision: 090c4c9
multimedia.jl
# This file is a part of Julia. License is MIT: https://julialang.org/license
module Multimedia
import .Base: show, print, convert, repr
export AbstractDisplay, display, pushdisplay, popdisplay, displayable, redisplay,
MIME, @MIME_str, istextmime,
showable, TextDisplay
###########################################################################
# We define a singleton type MIME{mime symbol} for each MIME type, so
# that Julia's dispatch and overloading mechanisms can be used to
# dispatch show and to add conversions for new types.
"""
MIME
A type representing a standard internet data format. "MIME" stands for
"Multipurpose Internet Mail Extensions", since the standard was originally
used to describe multimedia attachments to email messages.
A `MIME` object can be passed as the second argument to [`show`](@ref) to
request output in that format.
# Examples
```jldoctest
julia> show(stdout, MIME("text/plain"), "hi")
"hi"
```
"""
struct MIME{mime} end
"""
@MIME_str
A convenience macro for writing [`MIME`](@ref) types, typically used when
adding methods to [`show`](@ref).
For example the syntax `show(io::IO, ::MIME"text/html", x::MyType) = ...`
could be used to define how to write an HTML representation of `MyType`.
"""
macro MIME_str(s)
:(MIME{$(Expr(:quote, Symbol(s)))})
end
# fallback text/plain representation of any type:
show(io::IO, ::MIME"text/plain", x) = show(io, x)
MIME(s) = MIME{Symbol(s)}()
show(io::IO, ::MIME{mime}) where {mime} = print(io, "MIME type ", string(mime))
print(io::IO, ::MIME{mime}) where {mime} = print(io, mime)
###########################################################################
# For any type T one can define show(io, ::MIME"type", x::T) = ...
# in order to provide a way to export T as a given mime type.
"""
showable(mime, x)
Returns a boolean value indicating whether or not the object `x` can be written
as the given `mime` type.
(By default, this is determined automatically by the existence of the
corresponding [`show`](@ref) method for `typeof(x)`. Some types provide custom `showable`
methods; for example, if the available MIME formats depend on the *value* of `x`.)
# Examples
```jldoctest
julia> showable(MIME("text/plain"), rand(5))
true
julia> showable("img/png", rand(5))
false
```
"""
showable(::MIME{mime}, @nospecialize x) where {mime} = hasmethod(show, Tuple{IO, MIME{mime}, typeof(x)})
showable(m::AbstractString, @nospecialize x) = showable(MIME(m), x)
"""
show(io::IO, mime, x)
The [`display`](@ref) functions ultimately call `show` in order to write an object `x` as a
given `mime` type to a given I/O stream `io` (usually a memory buffer), if possible. In order
to provide a rich multimedia representation of a user-defined type `T`, it is only necessary
to define a new `show` method for `T`, via: `show(io, ::MIME"mime", x::T) = ...`,
where `mime` is a MIME-type string and the function body calls [`write`](@ref) (or similar) to write
that representation of `x` to `io`. (Note that the `MIME""` notation only supports
literal strings; to construct `MIME` types in a more flexible manner use
`MIME{Symbol("")}`.)
For example, if you define a `MyImage` type and know how to write it to a PNG file, you
could define a function `show(io, ::MIME"image/png", x::MyImage) = ...` to allow
your images to be displayed on any PNG-capable `AbstractDisplay` (such as IJulia). As usual, be sure
to `import Base.show` in order to add new methods to the built-in Julia function
`show`.
Technically, the `MIME"mime"` macro defines a singleton type for the given `mime` string,
which allows us to exploit Julia's dispatch mechanisms in determining how to display objects
of any given type.
The default MIME type is `MIME"text/plain"`. There is a fallback definition for `text/plain`
output that calls `show` with 2 arguments, so it is not always necessary to add a method
for that case. If a type benefits from custom human-readable output though,
`show(::IO, ::MIME"text/plain", ::T)` should be defined. For example, the `Day` type uses
`1 day` as the output for the `text/plain` MIME type, and `Day(1)` as the output of 2-argument `show`.
Container types generally implement 3-argument `show` by calling `show(io, MIME"text/plain"(), x)`
for elements `x`, with `:compact => true` set in an [`IOContext`](@ref) passed as the first argument.
"""
show(stream::IO, mime, x)
show(io::IO, m::AbstractString, x) = show(io, MIME(m), x)
"""
repr(mime, x; context=nothing)
Returns an `AbstractString` or `Vector{UInt8}` containing the representation of
`x` in the requested `mime` type, as written by [`show(io, mime, x)`](@ref) (throwing a
[`MethodError`](@ref) if no appropriate `show` is available). An `AbstractString` is
returned for MIME types with textual representations (such as `"text/html"` or
`"application/postscript"`), whereas binary data is returned as
`Vector{UInt8}`. (The function `istextmime(mime)` returns whether or not Julia
treats a given `mime` type as text.)
The optional keyword argument `context` can be set to `:key=>value` pair
or an `IO` or [`IOContext`](@ref) object whose attributes are used for the I/O
stream passed to `show`.
As a special case, if `x` is an `AbstractString` (for textual MIME types) or a
`Vector{UInt8}` (for binary MIME types), the `repr` function assumes that
`x` is already in the requested `mime` format and simply returns `x`. This
special case does not apply to the `"text/plain"` MIME type. This is useful so
that raw data can be passed to `display(m::MIME, x)`.
In particular, `repr("text/plain", x)` is typically a "pretty-printed" version
of `x` designed for human consumption. See also [`repr(x)`](@ref) to instead
return a string corresponding to [`show(x)`](@ref) that may be closer to how
the value of `x` would be entered in Julia.
# Examples
```jldoctest
julia> A = [1 2; 3 4];
julia> repr("text/plain", A)
"2×2 Matrix{Int64}:\\n 1 2\\n 3 4"
```
"""
repr(m::MIME, x; context=nothing) = istextmime(m) ? _textrepr(m, x, context) : _binrepr(m, x, context)
repr(m::AbstractString, x; context=nothing) = repr(MIME(m), x; context=context)
# strings are shown escaped for text/plain
_textrepr(m::MIME, x, context) = String(__binrepr(m, x, context))
_textrepr(::MIME, x::AbstractString, context) = x
_textrepr(m::MIME"text/plain", x::AbstractString, context) = String(__binrepr(m, x, context))
function __binrepr(m::MIME, x, context)
s = IOBuffer()
if context === nothing
show(s, m, x)
else
show(IOContext(s, context), m, x)
end
take!(s)
end
_binrepr(m::MIME, x, context) = __binrepr(m, x, context)
_binrepr(m::MIME, x::Vector{UInt8}, context) = x
"""
istextmime(m::MIME)
Determine whether a MIME type is text data. MIME types are assumed to be binary
data except for a set of types known to be text data (possibly Unicode).
# Examples
```jldoctest
julia> istextmime(MIME("text/plain"))
true
julia> istextmime(MIME("img/png"))
false
```
"""
istextmime(m::MIME) = startswith(string(m), "text/")
istextmime(m::AbstractString) = istextmime(MIME(m))
for mime in ["application/atom+xml", "application/ecmascript",
"application/javascript", "application/julia",
"application/json", "application/postscript",
"application/rdf+xml", "application/rss+xml",
"application/x-latex", "application/xhtml+xml", "application/xml",
"application/xml-dtd", "image/svg+xml", "model/vrml",
"model/x3d+vrml", "model/x3d+xml"]
global istextmime(::MIME{Symbol(mime)}) = true
end
###########################################################################
# We have an abstract AbstractDisplay class that can be subclassed in order to
# define new rich-display output devices. A typical subclass should
# overload display(d::AbstractDisplay, m::MIME, x) for supported MIME types m,
# (typically using show, repr, ..., to get the MIME
# representation of x) and should also overload display(d::AbstractDisplay, x)
# to display x in whatever MIME type is preferred by the AbstractDisplay and
# is writable by x. display(..., x) should throw a MethodError if x
# cannot be displayed. The return value of display(...) is up to the
# AbstractDisplay type.
"""
AbstractDisplay
Abstract supertype for rich display output devices. [`TextDisplay`](@ref) is a subtype
of this.
"""
abstract type AbstractDisplay end
# it is convenient to accept strings instead of ::MIME
display(d::AbstractDisplay, mime::AbstractString, @nospecialize x) = display(d, MIME(mime), x)
display(mime::AbstractString, @nospecialize x) = display(MIME(mime), x)
"""
displayable(mime) -> Bool
displayable(d::AbstractDisplay, mime) -> Bool
Returns a boolean value indicating whether the given `mime` type (string) is displayable by
any of the displays in the current display stack, or specifically by the display `d` in the
second variant.
"""
displayable(d::AbstractDisplay, mime::AbstractString) = displayable(d, MIME(mime))
displayable(mime::AbstractString) = displayable(MIME(mime))
# simplest display, which only knows how to display text/plain
"""
TextDisplay(io::IO)
Returns a `TextDisplay <: AbstractDisplay`, which displays any object as the text/plain MIME type
(by default), writing the text representation to the given I/O stream. (This is how
objects are printed in the Julia REPL.)
"""
struct TextDisplay <: AbstractDisplay
io::IO
end
display(d::TextDisplay, M::MIME"text/plain", @nospecialize x) = show(d.io, M, x)
display(d::TextDisplay, @nospecialize x) = display(d, MIME"text/plain"(), x)
# if you explicitly call display("text/foo", x), it should work on a TextDisplay:
displayable(d::TextDisplay, M::MIME) = istextmime(M)
function display(d::TextDisplay, M::MIME, @nospecialize x)
displayable(d, M) || throw(MethodError(display, (d, M, x)))
show(d.io, M, x)
end
import Base: close, flush
flush(d::TextDisplay) = flush(d.io)
close(d::TextDisplay) = close(d.io)
###########################################################################
# We keep a stack of Displays, and calling display(x) uses the topmost
# AbstractDisplay that is capable of displaying x (doesn't throw an error)
const displays = AbstractDisplay[]
"""
pushdisplay(d::AbstractDisplay)
Pushes a new display `d` on top of the global display-backend stack. Calling `display(x)` or
`display(mime, x)` will display `x` on the topmost compatible backend in the stack (i.e.,
the topmost backend that does not throw a [`MethodError`](@ref)).
"""
function pushdisplay(d::AbstractDisplay)
global displays
push!(displays, d)
end
"""
popdisplay()
popdisplay(d::AbstractDisplay)
Pop the topmost backend off of the display-backend stack, or the topmost copy of `d` in the
second variant.
"""
popdisplay() = pop!(displays)
function popdisplay(d::AbstractDisplay)
for i = length(displays):-1:1
if d == displays[i]
return splice!(displays, i)
end
end
throw(KeyError(d))
end
function reinit_displays()
empty!(displays)
pushdisplay(TextDisplay(stdout))
end
xdisplayable(D::AbstractDisplay, @nospecialize args...) = applicable(display, D, args...)
"""
display(x)
display(d::AbstractDisplay, x)
display(mime, x)
display(d::AbstractDisplay, mime, x)
AbstractDisplay `x` using the topmost applicable display in the display stack, typically using the
richest supported multimedia output for `x`, with plain-text [`stdout`](@ref) output as a fallback.
The `display(d, x)` variant attempts to display `x` on the given display `d` only, throwing
a [`MethodError`](@ref) if `d` cannot display objects of this type.
In general, you cannot assume that `display` output goes to `stdout` (unlike [`print(x)`](@ref) or
[`show(x)`](@ref)). For example, `display(x)` may open up a separate window with an image.
`display(x)` means "show `x` in the best way you can for the current output device(s)."
If you want REPL-like text output that is guaranteed to go to `stdout`, use
[`show(stdout, "text/plain", x)`](@ref) instead.
There are also two variants with a `mime` argument (a MIME type string, such as
`"image/png"`), which attempt to display `x` using the requested MIME type *only*, throwing
a `MethodError` if this type is not supported by either the display(s) or by `x`. With these
variants, one can also supply the "raw" data in the requested MIME type by passing
`x::AbstractString` (for MIME types with text-based storage, such as text/html or
application/postscript) or `x::Vector{UInt8}` (for binary MIME types).
To customize how instances of a type are displayed, overload [`show`](@ref) rather than `display`,
as explained in the manual section on [custom pretty-printing](@ref man-custom-pretty-printing).
"""
function display(@nospecialize x)
for i = length(displays):-1:1
if xdisplayable(displays[i], x)
try
return display(displays[i], x)
catch e
isa(e, MethodError) && (e.f === display || e.f === show) ||
rethrow()
end
end
end
throw(MethodError(display, (x,)))
end
function display(m::MIME, @nospecialize x)
for i = length(displays):-1:1
if xdisplayable(displays[i], m, x)
try
return display(displays[i], m, x)
catch e
isa(e, MethodError) && e.f == display ||
rethrow()
end
end
end
throw(MethodError(display, (m, x)))
end
displayable(d::D, ::MIME{mime}) where {D<:AbstractDisplay,mime} =
hasmethod(display, Tuple{D,MIME{mime},Any})
function displayable(m::MIME)
for d in displays
displayable(d, m) && return true
end
return false
end
###########################################################################
# The redisplay method can be overridden by a AbstractDisplay in order to
# update an existing display (instead of, for example, opening a new
# window), and is used by the IJulia interface to defer display
# until the next interactive prompt. This is especially useful
# for Matlab/Pylab-like stateful plotting interfaces, where
# a plot is created and then modified many times (xlabel, title, etc.).
"""
redisplay(x)
redisplay(d::AbstractDisplay, x)
redisplay(mime, x)
redisplay(d::AbstractDisplay, mime, x)
By default, the `redisplay` functions simply call [`display`](@ref).
However, some display backends may override `redisplay` to modify an existing
display of `x` (if any).
Using `redisplay` is also a hint to the backend that `x` may be redisplayed
several times, and the backend may choose to defer the display until
(for example) the next interactive prompt.
"""
function redisplay(@nospecialize x)
for i = length(displays):-1:1
if xdisplayable(displays[i], x)
try
return redisplay(displays[i], x)
catch e
isa(e, MethodError) && e.f in (redisplay, display, show) ||
rethrow()
end
end
end
throw(MethodError(redisplay, (x,)))
end
function redisplay(m::Union{MIME,AbstractString}, @nospecialize x)
for i = length(displays):-1:1
if xdisplayable(displays[i], m, x)
try
return redisplay(displays[i], m, x)
catch e
isa(e, MethodError) && e.f in (redisplay, display) ||
rethrow()
end
end
end
throw(MethodError(redisplay, (m, x)))
end
# default redisplay is simply to call display
redisplay(d::AbstractDisplay, @nospecialize x) = display(d, x)
redisplay(d::AbstractDisplay, m::Union{MIME,AbstractString}, @nospecialize x) = display(d, m, x)
###########################################################################
end # module