From afb0239caa4a477b5a4304338457c393efbb6026 Mon Sep 17 00:00:00 2001 From: Grant Bruer Date: Wed, 18 Sep 2024 15:28:44 -0400 Subject: [PATCH 1/3] Write display() calls in markdown output --- src/Literate.jl | 71 ++++++++++++++++++++++++++++-------------------- test/runtests.jl | 22 +++++++++++++++ 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/Literate.jl b/src/Literate.jl index 9ce052e..15dec7a 100644 --- a/src/Literate.jl +++ b/src/Literate.jl @@ -641,39 +641,21 @@ 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````" - 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") - 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 - end - if Base.invokelatest(showable, MIME("text/markdown"), r) - write(io, '\n') - Base.invokelatest(show, io, MIME("text/markdown"), r) - write(io, '\n') - return + + # Any explicit calls to display(...) + for dict in display_dicts + for (mime, data) in dict + display_markdown(io, data, outputdir, flavor, image_formats, file_prefix, plain_fence) 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 r !== nothing && !REPL.ends_with_semicolon(block) + 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') @@ -681,6 +663,37 @@ function execute_markdown!(io::IO, sb::Module, block::String, outputdir; 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, "![](", file, ")\n") + return + end + 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 const JUPYTER_VERSION = v"4.3.0" diff --git a/test/runtests.jl b/test/runtests.jl index 529231d..78b0224 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -985,6 +985,28 @@ 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 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.markdown(inputfile, outdir; execute=true) + markdown = read(joinpath(outdir, "inputfile.md"), String) + @test occursin("````\n\"DF(1) as text/plain\"\n````", markdown) + @test occursin("````\n\"DF(1) as text/html\"\n````", markdown) + @test occursin("````\n\"DF(2) as text/plain\"\n````", markdown) + @test occursin("````\n\"DF(2) as text/html\"\n````", markdown) + @test occursin("```@raw html\nDF(3) as text/html\n```", markdown) + @test occursin("````\n\"DF(4) as text/latex\"\n````", markdown) + # Softscope write( inputfile, From f26fb3db6afbc4bb4676d630aa28935f876d1f88 Mon Sep 17 00:00:00 2001 From: Grant Bruer Date: Fri, 20 Sep 2024 22:36:37 -0400 Subject: [PATCH 2/3] Implement mime chooser for markdown output --- src/Literate.jl | 56 ++++++++++++++++++++++++++++++++++++++++++------ test/runtests.jl | 51 ++++++++++++++++++++++++++++++++----------- 2 files changed, 89 insertions(+), 18 deletions(-) diff --git a/src/Literate.jl b/src/Literate.jl index 15dec7a..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 @@ -648,10 +648,8 @@ function execute_markdown!(io::IO, sb::Module, block::String, outputdir; plain_fence = "\n````\n" => "\n````" # Any explicit calls to display(...) - for dict in display_dicts - for (mime, data) in dict - display_markdown(io, data, outputdir, flavor, image_formats, file_prefix, plain_fence) - end + 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) @@ -678,7 +676,7 @@ function display_markdown(io, data, outputdir, flavor, image_formats, file_prefi open(joinpath(outputdir, file), "w") do io Base.invokelatest(show, io, mime, data) end - write(io, "![](", file, ")\n") + write(io, "\n![](", file, ")\n") return end end @@ -695,6 +693,52 @@ function display_markdown(io, data, outputdir, flavor, image_formats, file_prefi 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 + write(io, "\n![](", file, ")\n") + return + end + end + + if haskey(mime_dict, "text/latex") + data = mime_dict["text/latex"] + write(io, "\n```latex\n", data, "\n```\n") + return + 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 + + const JUPYTER_VERSION = v"4.3.0" parse_nbmeta(line::Pair) = parse_nbmeta(line.second) diff --git a/test/runtests.jl b/test/runtests.jl index 78b0224..e781d42 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -987,25 +987,52 @@ end end # 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") + 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"text/markdown", df::DF{3}) = print(io, "DF(\$(df.x)) as text/markdown") + Base.show(io::IO, ::MIME"text/latex", df::DF{4}) = print(io, "DF(\$(df.x)) as text/latex") + Base.show(io::IO, ::MIME"image/svg+xml", df::DF{5}) = print(io, "DF(\$(df.x)) as image/svg+xml") + Base.show(io::IO, ::MIME"image/png", df::DF{6}) = print(io, "DF(\$(df.x)) as image/png") #- - foreach(display, [DF(1), DF(2)]) - DF(3) + foreach(display, [DF(1), DF(2), DF(3), DF(4), DF(5)]) + DF(6) #- display(MIME("text/latex"), DF(4)) """ write(inputfile, script) Literate.markdown(inputfile, outdir; execute=true) markdown = read(joinpath(outdir, "inputfile.md"), String) - @test occursin("````\n\"DF(1) as text/plain\"\n````", markdown) - @test occursin("````\n\"DF(1) as text/html\"\n````", markdown) - @test occursin("````\n\"DF(2) as text/plain\"\n````", markdown) - @test occursin("````\n\"DF(2) as text/html\"\n````", markdown) - @test occursin("```@raw html\nDF(3) as text/html\n```", markdown) - @test occursin("````\n\"DF(4) as text/latex\"\n````", markdown) + + # Make sure each one shows up. + @test occursin("DF(1)", markdown) + @test occursin("DF(2)", markdown) + @test occursin("DF(3)", markdown) + @test occursin("DF(4)", markdown) + @test occursin("inputfile-3-5.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("DF(3)", markdown)[1] + @test findfirst("DF(3)", markdown)[1] < findfirst("DF(4)", markdown)[1] + @test findfirst("DF(4)", markdown)[1] < findfirst("inputfile-3-5.svg", markdown)[1] + @test findfirst("inputfile-3-5.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("\nDF(3) as text/markdown\n", markdown) + @test occursin("```latex\nDF(4) as text/latex\n```", markdown) + @test occursin("\n![](inputfile-3-5.svg)\n", markdown) + @test occursin("\n![](inputfile-3.png)\n", markdown) + + svg = read(joinpath(outdir, "inputfile-3-5.svg"), String) + @test svg == "DF(5) as image/svg+xml" + + png = read(joinpath(outdir, "inputfile-3.png"), String) + @test png == "DF(6) as image/png" # Softscope write( From 646d0d8e02f8f1ffe691b4b16cc8a9db79b05af1 Mon Sep 17 00:00:00 2001 From: Grant Bruer Date: Fri, 20 Sep 2024 23:33:40 -0400 Subject: [PATCH 3/3] Added another PNG test for coverage --- test/runtests.jl | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index e781d42..08a5d64 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -991,15 +991,14 @@ end 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"text/markdown", df::DF{3}) = print(io, "DF(\$(df.x)) as text/markdown") - Base.show(io::IO, ::MIME"text/latex", df::DF{4}) = print(io, "DF(\$(df.x)) as text/latex") - Base.show(io::IO, ::MIME"image/svg+xml", df::DF{5}) = print(io, "DF(\$(df.x)) as image/svg+xml") - Base.show(io::IO, ::MIME"image/png", df::DF{6}) = print(io, "DF(\$(df.x)) as image/png") + 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(1), DF(2), DF(3), DF(4), DF(5)]) - DF(6) - #- - display(MIME("text/latex"), DF(4)) + foreach(display, DF(i) for i in 1:6) + DF(7) """ write(inputfile, script) Literate.markdown(inputfile, outdir; execute=true) @@ -1008,31 +1007,37 @@ end end # Make sure each one shows up. @test occursin("DF(1)", markdown) @test occursin("DF(2)", markdown) - @test occursin("DF(3)", markdown) + @test occursin("inputfile-3-3.png", markdown) @test occursin("DF(4)", markdown) - @test occursin("inputfile-3-5.svg", 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("DF(3)", markdown)[1] - @test findfirst("DF(3)", markdown)[1] < findfirst("DF(4)", markdown)[1] - @test findfirst("DF(4)", markdown)[1] < findfirst("inputfile-3-5.svg", markdown)[1] - @test findfirst("inputfile-3-5.svg", markdown)[1] < findfirst("inputfile-3.png", 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("\nDF(3) as text/markdown\n", markdown) - @test occursin("```latex\nDF(4) as text/latex\n```", markdown) - @test occursin("\n![](inputfile-3-5.svg)\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) - svg = read(joinpath(outdir, "inputfile-3-5.svg"), String) - @test svg == "DF(5) as image/svg+xml" + 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" - png = read(joinpath(outdir, "inputfile-3.png"), String) - @test png == "DF(6) as image/png" + image = read(joinpath(outdir, "inputfile-3.png"), String) + @test image == "DF(7) as image/png" # Softscope write(