Browse Source

Capture output from display(x) and display(mime, x)

This implement a simple LiterateDisplay and pushes that
to the display stack when executing code to capture manual
calls to display(x) and display(mime, x), fixes #128.
pull/133/head
Fredrik Ekre 5 years ago
parent
commit
ceff7a36be
  1. 1
      src/IJulia.jl
  2. 65
      src/Literate.jl
  3. 38
      test/runtests.jl

1
src/IJulia.jl

@ -29,6 +29,7 @@ function display_dict(x) @@ -29,6 +29,7 @@ function display_dict(x)
elseif showable(image_jpeg, x) # don't send jpeg if we have png
data[string(image_jpeg)] = limitstringmime(image_jpeg, x)
end
# TODO: IJulia logic has changed? Seems to send both html and latex for DataFrame for example
if showable(text_markdown, x)
data[string(text_markdown)] = limitstringmime(text_markdown, x)
elseif showable(text_html, x)

65
src/Literate.jl

@ -475,7 +475,8 @@ function markdown(inputfile, outputdir=pwd(); config::Dict=Dict(), kwargs...) @@ -475,7 +475,8 @@ function markdown(inputfile, outputdir=pwd(); config::Dict=Dict(), kwargs...)
end
function execute_markdown!(io::IO, sb::Module, block::String, outputdir)
r, str = execute_block(sb, block)
# TODO: Deal with explicit display(...) calls
r, str, _ = execute_block(sb, block)
plain_fence = "\n```\n" => "\n```" # issue #101: consecutive codefenced blocks need newline
if r !== nothing && !REPL.ends_with_semicolon(block)
for (mime, ext) in [(MIME("image/png"), ".png"), (MIME("image/jpeg"), ".jpeg")]
@ -621,7 +622,7 @@ function execute_notebook(nb) @@ -621,7 +622,7 @@ function execute_notebook(nb)
execution_count += 1
cell["execution_count"] = execution_count
block = join(cell["source"])
r, str = execute_block(sb, block)
r, str, display_dicts = execute_block(sb, block)
# str should go into stream
if !isempty(str)
@ -632,6 +633,27 @@ function execute_notebook(nb) @@ -632,6 +633,27 @@ function execute_notebook(nb)
push!(cell["outputs"], stream)
end
# Some mimes need to be split into vectors of lines instead of a single string
# TODO: Seems like text/plain and text/latex are also split now, but not doing
# it seems to work fine. Leave for now.
function split_mime(dict)
for mime in ("image/svg+xml", "text/html")
if haskey(dict, mime)
dict[mime] = collect(Any, eachline(IOBuffer(dict[mime]), keep = true))
end
end
return dict
end
# Any explicit calls to display(...)
for dict in display_dicts
display_data = Dict{String,Any}()
display_data["output_type"] = "display_data"
display_data["metadata"] = Dict()
display_data["data"] = split_mime(dict)
push!(cell["outputs"], display_data)
end
# check if ; is used to suppress output
r = REPL.ends_with_semicolon(block) ? nothing : r
@ -641,18 +663,10 @@ function execute_notebook(nb) @@ -641,18 +663,10 @@ function execute_notebook(nb)
execute_result["output_type"] = "execute_result"
execute_result["metadata"] = Dict()
execute_result["execution_count"] = execution_count
dd = Base.invokelatest(IJulia.display_dict, r)
# we need to split some mime types into vectors of lines instead of a single string
for mime in ("image/svg+xml", "text/html")
if haskey(dd, mime)
dd[mime] = collect(Any, eachline(IOBuffer(dd[mime]), keep = true))
end
end
execute_result["data"] = dd
dict = Base.invokelatest(IJulia.display_dict, r)
execute_result["data"] = split_mime(dict)
push!(cell["outputs"], execute_result)
end
end
return nb
end
@ -668,8 +682,32 @@ function sandbox() @@ -668,8 +682,32 @@ function sandbox()
return m
end
# Capture display for notebooks
struct LiterateDisplay <: AbstractDisplay
data::Vector
LiterateDisplay() = new([])
end
function Base.display(ld::LiterateDisplay, x)
push!(ld.data, Base.invokelatest(IJulia.display_dict, x))
return nothing
end
# TODO: Problematic to accept mime::MIME here?
function Base.display(ld::LiterateDisplay, mime::MIME, x)
r = Base.invokelatest(IJulia.limitstringmime, mime, x)
display_dicts = Dict{String,Any}(string(mime) => r)
# TODO: IJulia does this part below for unknown mimes
# if istextmime(mime)
# display_dicts["text/plain"] = r
# end
push!(ld.data, display_dicts)
return nothing
end
# Execute a code-block in a module and capture stdout/stderr and the result
function execute_block(sb::Module, block::String)
# Push a capturing display on the displaystack
disp = LiterateDisplay()
pushdisplay(disp)
# r is the result
# status = (true|false)
# _: backtrace
@ -677,6 +715,7 @@ function execute_block(sb::Module, block::String) @@ -677,6 +715,7 @@ function execute_block(sb::Module, block::String)
r, status, _, str = Documenter.withoutput() do
include_string(sb, block)
end
popdisplay(disp) # Documenter.withoutput has a try-catch so should always end up here
if !status
error("""
$(sprint(showerror, r))
@ -687,7 +726,7 @@ function execute_block(sb::Module, block::String) @@ -687,7 +726,7 @@ function execute_block(sb::Module, block::String)
```
""")
end
return r, str
return r, str, disp.data
end
end # module

38
test/runtests.jl

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import Literate
import Literate, JSON
import Literate: Chunk, MDChunk, CodeChunk
using Test
@ -1110,6 +1110,42 @@ end end @@ -1110,6 +1110,42 @@ end end
end
write(inputfile, script)
Literate.notebook(inputfile, outdir)
# Calls to display(x) and display(mime, x)
script = """
struct DF x end
Base.show(io::IO, ::MIME"text/plain", df::DF) = print(io, "DF(\$(df.x)) as text/plain")
Base.show(io::IO, ::MIME"text/html", df::DF) = print(io, "DF(\$(df.x)) as text/html")
Base.show(io::IO, ::MIME"text/latex", df::DF) = print(io, "DF(\$(df.x)) as text/latex")
#-
foreach(display, [DF(1), DF(2)])
DF(3)
#-
display(MIME("text/latex"), DF(4))
"""
write(inputfile, script)
Literate.notebook(inputfile, outdir)
json = JSON.parsefile(joinpath(outdir, "inputfile.ipynb"))
cells = json["cells"]
@test length(cells) == 4
# Cell 2 has 3 outputs: 2 display and one execute result
cellout = cells[2]["outputs"]
@test length(cellout) == 3
for i in 1:3
exe_res = i == 3
@test cellout[i]["output_type"] == (exe_res ? "execute_result" : "display_data")
@test keys(cellout[i]["data"]) == Set(("text/plain", "text/html"))
@test cellout[i]["data"]["text/plain"] == "DF($i) as text/plain"
@test cellout[i]["data"]["text/html"] == Any["DF($i) as text/html"]
@test haskey(cellout[i], "execution_count") == exe_res
end
# Cell 3 has one output from a single display call
cellout = cells[3]["outputs"]
@test length(cellout) == 1
@test cellout[1]["output_type"] == "display_data"
@test keys(cellout[1]["data"]) == Set(("text/latex",))
@test cellout[1]["data"]["text/latex"] == "DF(4) as text/latex"
@test !haskey(cellout[1], "execution_count")
end
end
end end

Loading…
Cancel
Save