From 445cfda77d7e6b10ca34dd8730c9ac54200d8f9a Mon Sep 17 00:00:00 2001 From: Fredrik Ekre Date: Tue, 6 Jul 2021 01:31:50 +0200 Subject: [PATCH] Support prerendering of code blocks. --- src/Writers/HTMLWriter.jl | 91 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/src/Writers/HTMLWriter.jl b/src/Writers/HTMLWriter.jl index 4f52d9b6a8f..5d6193964cc 100644 --- a/src/Writers/HTMLWriter.jl +++ b/src/Writers/HTMLWriter.jl @@ -388,6 +388,10 @@ struct HTML <: Documenter.Writer highlights :: Vector{String} mathengine :: Union{MathEngine,Nothing} footer :: Union{Markdown.MD, Nothing} + prerender :: Bool + node :: Union{Cmd,String,Nothing} + highlightjs :: Union{String,Nothing} + lang :: String warn_outdated :: Bool @@ -403,12 +407,54 @@ struct HTML <: Documenter.Writer highlights :: Vector{String} = String[], mathengine :: Union{MathEngine,Nothing} = KaTeX(), footer :: Union{String, Nothing} = "Powered by [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl) and the [Julia Programming Language](https://julialang.org/).", + # Code block prerendering + prerender :: Bool = false, + node :: Union{Cmd,String,Nothing} = nothing, + highlightjs :: Union{String,Nothing} = nothing, + # deprecated keywords edit_branch :: Union{String, Nothing, Default} = Default(nothing), lang :: String = "en", - warn_outdated :: Bool = true + warn_outdated :: Bool = true, ) collapselevel >= 1 || throw(ArgumentError("collapselevel must be >= 1")) + if prerender + node = node === nothing ? Sys.which("node") : node + if node === nothing + @error "HTMLWriter: no node executable given or found on the system. Setting `prerender=false`." + prerender = false + else + if !success(`$node --version`) + @error "HTMLWriter: bad node executable at $node. Setting `prerender=false`." + prerender = false + end + end + end + if prerender && highlightjs === nothing + # Try to download + curl = Sys.which("curl") + if curl === nothing + @error "HTMLWriter: no highlight.js file given and no curl executable found " * + "on the system. Setting `prerender=false`." + prerender = false + else + @debug "HTMLWriter: downloading highlightjs" + r = Utilities.JSDependencies.RequireJS([]) + RD.highlightjs!(r, highlights) + libs = sort!(collect(r.libraries); by = first) # puts highlight first + key = join((x.first for x in libs), ',') + highlightjs = get!(HLJSFILES, key) do + path, io = mktemp() + for lib in libs + println(io, "// $(lib.first)") + run(pipeline(`$(curl) -fsSL $(lib.second.url)`; stdout=io)) + println(io) + end + close(io) + return path + end + end + end assets = map(assets) do asset isa(asset, HTMLAsset) && return asset isa(asset, AbstractString) && return HTMLAsset(assetclass(asset), asset, true) @@ -434,10 +480,14 @@ struct HTML <: Documenter.Writer end isa(edit_link, Default) && (edit_link = edit_link[]) new(prettyurls, disable_git, edit_link, canonical, assets, analytics, - collapselevel, sidebar_sitename, highlights, mathengine, footer, lang, warn_outdated) + collapselevel, sidebar_sitename, highlights, mathengine, footer, + prerender, node, highlightjs, lang, warn_outdated) end end +# Cache of downloaded highlight.js bundles +const HLJSFILES = Dict{String,String}() + "Provides a namespace for remote dependencies." module RD using JSON @@ -479,7 +529,7 @@ module RD "highlight", "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/$(hljs_version)/highlight.min.js" )) - prepend!(languages, ["julia", "julia-repl"]) + languages = ["julia", "julia-repl", languages...] for language in languages language = jsescape(language) push!(r, RemoteLibrary( @@ -657,7 +707,9 @@ function render(doc::Documents.Document, settings::HTML=HTML()) RD.jquery, RD.jqueryui, RD.headroom, RD.headroom_jquery, ]) RD.mathengine!(r, settings.mathengine) - RD.highlightjs!(r, settings.highlights) + if !settings.prerender + RD.highlightjs!(r, settings.highlights) + end for filename in readdir(joinpath(ASSETS, "js")) path = joinpath(ASSETS, "js", filename) endswith(filename, ".js") && isfile(path) || continue @@ -1358,7 +1410,7 @@ end function domify(ctx, navnode, node) fixlinks!(ctx, navnode, node) - mdconvert(node, Markdown.MD(); footnotes=ctx.footnotes) + mdconvert(node, Markdown.MD(); footnotes=ctx.footnotes, settings=ctx.settings) end function domify(ctx, navnode, anchor::Anchors.Anchor) @@ -1656,14 +1708,39 @@ mdconvert(b::Markdown.BlockQuote, parent; kwargs...) = Tag(:blockquote)(mdconver mdconvert(b::Markdown.Bold, parent; kwargs...) = Tag(:strong)(mdconvert(b.text, parent; kwargs...)) -function mdconvert(c::Markdown.Code, parent::MDBlockContext; kwargs...) +function mdconvert(c::Markdown.Code, parent::MDBlockContext; settings::HTML, kwargs...) @tags pre code language = Utilities.codelang(c.language) class = isempty(language) ? "nohighlight" : "language-$(language)" - pre(code[".$class"](c.code)) + if settings.prerender && !(isempty(language) || language == "nohighlight") + r = hljs_prerender(c, settings) + r !== nothing && return r + end + return pre(code[".$class"](c.code)) end mdconvert(c::Markdown.Code, parent; kwargs...) = Tag(:code)(c.code) +function hljs_prerender(c::Markdown.Code, settings) + @tags pre code + lang = Utilities.codelang(c.language) + hljs = settings.highlightjs + js = """ + const hljs = require('$(hljs)'); + console.log(hljs.highlight($(repr(c.code)), {'language': "$(lang)"}).value); + """ + out, err = IOBuffer(), IOBuffer() + try + run(pipeline(`$(settings.node) -e "$(js)"`; stdout=out, stderr=err)) + str = String(take!(out)) + # prepend nohighlight to stop runtime highlighting + # return pre(code[".nohighlight $(lang) .hljs"](Tag(Symbol("#RAW#"))(str))) + return pre(code[".language-$(lang) .hljs"](Tag(Symbol("#RAW#"))(str))) + catch e + @error "HTMLWriter: prerendering failed" exception=e stderr=String(take!(err)) + end + return nothing +end + mdconvert(h::Markdown.Header{N}, parent; kwargs...) where {N} = DOM.Tag(Symbol("h$N"))(mdconvert(h.text, h; kwargs...)) mdconvert(::Markdown.HorizontalRule, parent; kwargs...) = Tag(:hr)()