diff --git a/src/Literate.jl b/src/Literate.jl index 9ce052e..a54fabb 100644 --- a/src/Literate.jl +++ b/src/Literate.jl @@ -6,7 +6,7 @@ https://fredrikekre.github.io/Literate.jl/ for documentation. """ module Literate -import JSON, REPL, IOCapture +import JSON, REPL, IOCapture, Base64 include("IJulia.jl") import .IJulia @@ -641,44 +641,101 @@ function execute_markdown!(io::IO, sb::Module, block::String, outputdir; flavor::AbstractFlavor, image_formats::Vector, file_prefix::String, softscope::Bool) # TODO: Deal with explicit display(...) calls - r, str, _ = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source, softscope=softscope) + r, str, display_dicts = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source, softscope=softscope) + # issue #101: consecutive codefenced blocks need newline # issue #144: quadruple backticks allow for triple backticks in the output plain_fence = "\n````\n" => "\n````" + + # Any explicit calls to display(...) + for (i, d) in enumerate(display_dicts) + display_markdown_mime(io, d, outputdir, flavor, image_formats, "$(file_prefix)-$i", plain_fence) + end + if r !== nothing && !REPL.ends_with_semicolon(block) - if (flavor isa FranklinFlavor || flavor isa DocumenterFlavor) && - Base.invokelatest(showable, MIME("text/html"), r) - htmlfence = flavor isa FranklinFlavor ? ("~~~" => "~~~") : ("```@raw html" => "```") - write(io, "\n", htmlfence.first, "\n") - Base.invokelatest(show, io, MIME("text/html"), r) - write(io, "\n", htmlfence.second, "\n") + display_markdown(io, r, outputdir, flavor, image_formats, file_prefix, plain_fence) + return + elseif !isempty(str) + write(io, plain_fence.first, str, plain_fence.second, '\n') + return + end +end + +function display_markdown(io, data, outputdir, flavor, image_formats, file_prefix, plain_fence) + if (flavor isa FranklinFlavor || flavor isa DocumenterFlavor) && + Base.invokelatest(showable, MIME("text/html"), data) + htmlfence = flavor isa FranklinFlavor ? ("~~~" => "~~~") : ("```@raw html" => "```") + write(io, "\n", htmlfence.first, "\n") + Base.invokelatest(show, io, MIME("text/html"), data) + write(io, "\n", htmlfence.second, "\n") + return + end + for (mime, ext) in image_formats + if Base.invokelatest(showable, mime, data) + file = file_prefix * ext + open(joinpath(outputdir, file), "w") do io + Base.invokelatest(show, io, mime, data) + end + write(io, "\n![](", file, ")\n") return end - for (mime, ext) in image_formats - if Base.invokelatest(showable, mime, r) - file = file_prefix * ext - open(joinpath(outputdir, file), "w") do io - Base.invokelatest(show, io, mime, r) - end - write(io, "![](", file, ")\n") - return + end + if Base.invokelatest(showable, MIME("text/markdown"), data) + write(io, '\n') + Base.invokelatest(show, io, MIME("text/markdown"), data) + write(io, '\n') + return + end + # fallback to text/plain + write(io, plain_fence.first) + Base.invokelatest(show, io, "text/plain", data) + write(io, plain_fence.second, '\n') + return +end + +function display_markdown_mime(io, mime_dict, outputdir, flavor, image_formats, file_prefix, plain_fence) + if (flavor isa FranklinFlavor || flavor isa DocumenterFlavor) && + haskey(mime_dict, "text/html") + htmlfence = flavor isa FranklinFlavor ? ("~~~" => "~~~") : ("```@raw html" => "```") + data = mime_dict["text/html"] + write(io, "\n", htmlfence.first, "\n") + write(io, data) + write(io, "\n", htmlfence.second, "\n") + return + end + + for (mime, ext) in image_formats + if haskey(mime_dict, string(mime)) + data = mime_dict[string(mime)] + file = file_prefix * ext + if istextmime(mime) + write(joinpath(outputdir, file), data) + else + data_decoded = Base64.base64decode(data) + write(joinpath(outputdir, file), data_decoded) end - end - if Base.invokelatest(showable, MIME("text/markdown"), r) - write(io, '\n') - Base.invokelatest(show, io, MIME("text/markdown"), r) - write(io, '\n') + write(io, "\n![](", file, ")\n") return end - # fallback to text/plain - write(io, plain_fence.first) - Base.invokelatest(show, io, "text/plain", r) - write(io, plain_fence.second, '\n') + end + + if haskey(mime_dict, "text/latex") + data = mime_dict["text/latex"] + write(io, "\n```latex\n", data, "\n```\n") return - elseif !isempty(str) - write(io, plain_fence.first, str, plain_fence.second, '\n') + end + + if haskey(mime_dict, "text/markdown") + data = mime_dict["text/markdown"] + write(io, '\n', data, '\n') return end + + # fallback to text/plain + @assert haskey(mime_dict, "text/plain") + write(io, plain_fence.first) + write(io, mime_dict["text/plain"]) + write(io, plain_fence.second, '\n') end diff --git a/test/runtests.jl b/test/runtests.jl index 529231d..08a5d64 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -985,6 +985,60 @@ end end flavor=Literate.CommonMarkFlavor()) @test read(joinpath(outdir, "inputfile-1.svg"), String) == "issue228" + # Calls to display(x) and display(mime, x) + script = """ + struct DF{N} x end + DF(x) = DF{x}(x) + Base.show(io::IO, ::MIME"text/plain", df::DF{1}) = print(io, "DF(\$(df.x)) as text/plain") + Base.show(io::IO, ::MIME"text/html", df::DF{2}) = print(io, "DF(\$(df.x)) as text/html") + Base.show(io::IO, ::MIME"image/png", df::DF{3}) = print(io, "DF(\$(df.x)) as image/png") + Base.show(io::IO, ::MIME"text/markdown", df::DF{4}) = print(io, "DF(\$(df.x)) as text/markdown") + Base.show(io::IO, ::MIME"text/latex", df::DF{5}) = print(io, "DF(\$(df.x)) as text/latex") + Base.show(io::IO, ::MIME"image/svg+xml", df::DF{6}) = print(io, "DF(\$(df.x)) as image/svg+xml") + Base.show(io::IO, ::MIME"image/png", df::DF{7}) = print(io, "DF(\$(df.x)) as image/png") + #- + foreach(display, DF(i) for i in 1:6) + DF(7) + """ + write(inputfile, script) + Literate.markdown(inputfile, outdir; execute=true) + markdown = read(joinpath(outdir, "inputfile.md"), String) + + # Make sure each one shows up. + @test occursin("DF(1)", markdown) + @test occursin("DF(2)", markdown) + @test occursin("inputfile-3-3.png", markdown) + @test occursin("DF(4)", markdown) + @test occursin("DF(5)", markdown) + @test occursin("inputfile-3-6.svg", markdown) + @test occursin("inputfile-3.png", markdown) + + # Check the ordering. + @test findfirst("DF(1)", markdown)[1] < findfirst("DF(2)", markdown)[1] + @test findfirst("DF(2)", markdown)[1] < findfirst("inputfile-3-3.png", markdown)[1] + @test findfirst("inputfile-3-3.png", markdown)[1] < findfirst("DF(4)", markdown)[1] + @test findfirst("DF(4)", markdown)[1] < findfirst("DF(5)", markdown)[1] + @test findfirst("DF(5)", markdown)[1] < findfirst("inputfile-3-6.svg", markdown)[1] + @test findfirst("inputfile-3-6.svg", markdown)[1] < findfirst("inputfile-3.png", markdown)[1] + + # Check the formatting. + @test occursin("````\nDF(1) as text/plain\n````", markdown) + @test occursin("```@raw html\nDF(2) as text/html\n```", markdown) + @test occursin("\n![](inputfile-3-3.png)\n", markdown) + @test occursin("\nDF(4) as text/markdown\n", markdown) + @test occursin("```latex\nDF(5) as text/latex\n```", markdown) + @test occursin("\n![](inputfile-3-6.svg)\n", markdown) + @test occursin("\n![](inputfile-3.png)\n", markdown) + + image = read(joinpath(outdir, "inputfile-3-3.png"), String) + @test image == "DF(3) as image/png" + + image = read(joinpath(outdir, "inputfile-3-6.svg"), String) + @test image == "DF(6) as image/svg+xml" + + image = read(joinpath(outdir, "inputfile-3.png"), String) + @test image == "DF(7) as image/png" + # Softscope write( inputfile,