import Mustache, Highlights import .WeaveMarkdown using Dates using Markdown using REPL.REPLCompletions: latex_symbols function format(doc::WeaveDoc) formatted = AbstractString[] docformat = doc.format # Complete format dictionaries with defaults formatdict = docformat.formatdict get!(formatdict, :termstart, formatdict[:codestart]) get!(formatdict, :termend, formatdict[:codeend]) get!(formatdict, :out_width, nothing) get!(formatdict, :out_height, nothing) get!(formatdict, :fig_pos, nothing) get!(formatdict, :fig_env, nothing) docformat.formatdict[:cwd] = doc.cwd # pass wd to figure formatters docformat.formatdict[:theme] = doc.highlight_theme strip_header!(doc) for chunk in copy(doc.chunks) result = format_chunk(chunk, formatdict, docformat) push!(formatted, result) end formatted = join(formatted, "\n") # Render using a template if needed rendered = render_doc(formatted, doc, doc.format) return rendered end """ render_doc(formatted::AbstractString, format) Render formatted document to a template """ function render_doc(formatted, doc::WeaveDoc, format) return formatted end function highlight( mime::MIME, source::AbstractString, lexer, theme = Highlights.Themes.DefaultTheme, ) return sprint((io, x) -> Highlights.highlight(io, mime, x, lexer, theme), source) end function stylesheet(m::MIME, theme) return sprint((io, x) -> Highlights.stylesheet(io, m, x), theme) end function render_doc(formatted, doc::WeaveDoc, format::JMarkdown2HTML) css = stylesheet(MIME("text/html"), doc.highlight_theme) path, wsource = splitdir(abspath(doc.source)) # wversion = string(Pkg.installed("Weave")) wversion = "" wtime = string(Date(now())) if isempty(doc.css) theme_css = read(joinpath(dirname(@__FILE__), "../templates/skeleton_css.css"), String) else theme_css = read(doc.css, String) end if isa(doc.template, Mustache.MustacheTokens) template = doc.template elseif isempty(doc.template) template = Mustache.template_from_file(joinpath( dirname(@__FILE__), "../templates/julia_html.tpl", )) else template = Mustache.template_from_file(doc.template) end return Mustache.render( template; themecss = theme_css, highlightcss = css, body = formatted, header_script = doc.header_script, source = wsource, wtime = wtime, wversion = wversion, [Pair(Symbol(k), v) for (k, v) in doc.header]..., ) end function render_doc(formatted, doc::WeaveDoc, format::JMarkdown2tex) highlight = stylesheet(MIME("text/latex"), doc.highlight_theme) path, wsource = splitdir(abspath(doc.source)) # wversion = string(Pkg.installed("Weave")) wversion = "" wtime = string(Date(now())) if isa(doc.template, Mustache.MustacheTokens) template = doc.template elseif isempty(doc.template) template = Mustache.template_from_file(joinpath( dirname(@__FILE__), "../templates/julia_tex.tpl", )) else template = Mustache.template_from_file(doc.template) end return Mustache.render( template; body = formatted, highlight = highlight, [Pair(Symbol(k), v) for (k, v) in doc.header]..., ) end strip_header!(doc::WeaveDoc) = strip_header!(doc.chunks[1], doc.doctype) function strip_header!(docchunk::DocChunk, doctype) doctype == "pandoc" && return content = docchunk.content[1].content if (m = match(HEADER_REGEX, content)) !== nothing # TODO: is there other format where we want to keep headers ? docchunk.content[1].content = if doctype != "github" lstrip(replace(content, HEADER_REGEX => "")) else # only strips Weave headers header = YAML.load(m[:header]) delete!(header, WEAVE_OPTION_NAME) if isempty(header) lstrip(replace(content, HEADER_REGEX => "")) else lstrip(replace(content, HEADER_REGEX => "---\n$(YAML.write(header))---")) end end end end strip_header!(codechunk::CodeChunk, doctype) = return function format_chunk(chunk::DocChunk, formatdict, docformat) return join([format_inline(c) for c in chunk.content], "") end function format_inline(inline::InlineText) return inline.content end function format_inline(inline::InlineCode) isempty(inline.rich_output) || return inline.rich_output isempty(inline.figures) || return inline.figures[end] isempty(inline.output) || return inline.output end function ioformat!(io::IOBuffer, out::IOBuffer, fun = WeaveMarkdown.latex) text = String(take!(io)) if !isempty(text) m = Markdown.parse(text, flavor = WeaveMarkdown.weavemd) write(out, string(fun(m))) end end function addspace(op, inline) inline.ctype == :line && (op = "\n$op\n") return op end function format_chunk(chunk::DocChunk, formatdict, docformat::JMarkdown2tex) out = IOBuffer() io = IOBuffer() for inline in chunk.content if isa(inline, InlineText) write(io, inline.content) elseif !isempty(inline.rich_output) ioformat!(io, out) write(out, addspace(inline.rich_output, inline)) elseif !isempty(inline.figures) write(io, inline.figures[end], inline) elseif !isempty(inline.output) write(io, addspace(inline.output, inline)) end end ioformat!(io, out) formatdict[:keep_unicode] || return uc2tex(String(take!(out))) return String(take!(out)) end function format_chunk(chunk::DocChunk, formatdict, docformat::JMarkdown2HTML) out = IOBuffer() io = IOBuffer() fun = WeaveMarkdown.html for inline in chunk.content if isa(inline, InlineText) write(io, inline.content) elseif !isempty(inline.rich_output) ioformat!(io, out, fun) write(out, addspace(inline.rich_output, inline)) elseif !isempty(inline.figures) write(io, inline.figures[end]) elseif !isempty(inline.output) write(io, addspace(inline.output, inline)) end end ioformat!(io, out, fun) return String(take!(out)) end function format_chunk(chunk::CodeChunk, formatdict, docformat) # Fill undefined options with format specific defaults chunk.options[:out_width] == nothing && (chunk.options[:out_width] = formatdict[:out_width]) chunk.options[:fig_pos] == nothing && (chunk.options[:fig_pos] = formatdict[:fig_pos]) # Only use floats if chunk has caption or sets fig_env if chunk.options[:fig_cap] != nothing && chunk.options[:fig_env] == nothing (chunk.options[:fig_env] = formatdict[:fig_env]) end if haskey(formatdict, :indent) chunk.content = indent(chunk.content, formatdict[:indent]) end chunk.content = format_code(chunk.content, docformat) if !chunk.options[:eval] if chunk.options[:echo] result = "$(formatdict[:codestart])\n$(chunk.content)$(formatdict[:codeend])" return result else r = "" return r end end if chunk.options[:term] result = format_termchunk(chunk, formatdict, docformat) else if chunk.options[:echo] # Convert to output format and highlight (html, tex...) if needed result = "$(formatdict[:codestart])$(chunk.content)$(formatdict[:codeend])\n" else result = "" end if (strip(chunk.output) != "" || strip(chunk.rich_output) != "") && (chunk.options[:results] != "hidden") if chunk.options[:results] != "markup" && chunk.options[:results] != "hold" strip(chunk.output) ≠ "" && (result *= "$(chunk.output)\n") strip(chunk.rich_output) ≠ "" && (result *= "$(chunk.rich_output)\n") else if chunk.options[:wrap] chunk.output = "\n" * wraplines(chunk.output, chunk.options[:line_width]) chunk.output = format_output(chunk.output, docformat) else chunk.output = "\n" * rstrip(chunk.output) chunk.output = format_output(chunk.output, docformat) end if haskey(formatdict, :indent) chunk.output = indent(chunk.output, formatdict[:indent]) end strip(chunk.output) ≠ "" && ( result *= "$(formatdict[:outputstart])$(chunk.output)\n$(formatdict[:outputend])\n" ) strip(chunk.rich_output) ≠ "" && (result *= chunk.rich_output * "\n") end end end # Handle figures if chunk.options[:fig] && length(chunk.figures) > 0 if chunk.options[:include] result *= formatfigures(chunk, docformat) end end return result end function format_output(result::AbstractString, docformat) return result end function format_output(result::AbstractString, docformat::JMarkdown2HTML) return Markdown.htmlesc(result) end function format_output(result::AbstractString, docformat::JMarkdown2tex) # Highligts has some extra escaping defined, eg of $, ", ... result_escaped = sprint( (io, x) -> Highlights.Format.escape(io, MIME("text/latex"), x, charescape = true), result, ) docformat.formatdict[:keep_unicode] || return uc2tex(result_escaped, true) return result_escaped end function format_code(result::AbstractString, docformat) return result end function format_code(result::AbstractString, docformat::JMarkdown2tex) highlighted = highlight( MIME("text/latex"), strip(result), Highlights.Lexers.JuliaLexer, docformat.formatdict[:theme], ) docformat.formatdict[:keep_unicode] || return uc2tex(highlighted) return highlighted # return "\\begin{minted}[mathescape, fontsize=\\small, xleftmargin=0.5em]{julia}\n$result\n\\end{minted}\n" end # Convert unicode to tex, escape listings if needed function uc2tex(s, escape = false) for key in keys(latex_symbols) if escape s = replace(s, latex_symbols[key] => "(*@\\ensuremath{$(texify(key))}@*)") else s = replace(s, latex_symbols[key] => "\\ensuremath{$(texify(key))}") end end return s end # Make julia symbols (\bf* etc.) valid latex function texify(s) ts = "" if occursin(r"^\\bf[A-Z]$", s) ts = replace(s, "\\bf" => "\\bm{\\mathrm{") * "}}" elseif startswith(s, "\\bfrak") ts = replace(s, "\\bfrak" => "\\bm{\\mathfrak{") * "}}" elseif startswith(s, "\\bf") ts = replace(s, "\\bf" => "\\bm{\\") * "}" elseif startswith(s, "\\frak") ts = replace(s, "\\frak" => "\\mathfrak{") * "}" else ts = s end return ts end function format_code(result::AbstractString, docformat::JMarkdown2HTML) return highlight( MIME("text/html"), strip(result), Highlights.Lexers.JuliaLexer, docformat.formatdict[:theme], ) end function format_code(result::AbstractString, docformat::Pandoc2HTML) return highlight( MIME("text/html"), strip(result), Highlights.Lexers.JuliaLexer, docformat.formatdict[:theme], ) end function format_termchunk(chunk, formatdict, docformat) if chunk.options[:echo] && chunk.options[:results] != "hidden" result = "$(formatdict[:termstart])$(chunk.output)\n" * "$(formatdict[:termend])\n" else result = "" end return result end function format_termchunk(chunk, formatdict, docformat::JMarkdown2HTML) if chunk.options[:echo] && chunk.options[:results] != "hidden" result = highlight( MIME("text/html"), strip(chunk.output), Highlights.Lexers.JuliaConsoleLexer, docformat.formatdict[:theme], ) else result = "" end return result end function format_termchunk(chunk, formatdict, docformat::Pandoc2HTML) if chunk.options[:echo] && chunk.options[:results] != "hidden" result = highlight( MIME("text/html"), strip(chunk.output), Highlights.Lexers.JuliaConsoleLexer, docformat.formatdict[:theme], ) else result = "" end return result end function format_termchunk(chunk, formatdict, docformat::JMarkdown2tex) if chunk.options[:echo] && chunk.options[:results] != "hidden" result = highlight( MIME("text/latex"), strip(chunk.output), Highlights.Lexers.JuliaConsoleLexer, docformat.formatdict[:theme], ) # return "\\begin{minted}[mathescape, fontsize=\\small, xleftmargin=0.5em]{julia}\n$result\n\\end{minted}\n" else result = "" end return result end function indent(text, nindent) return join(map(x -> string(repeat(" ", nindent), x), split(text, "\n")), "\n") end function wraplines(text, line_width = 75) result = AbstractString[] lines = split(text, "\n") for line in lines if length(line) > line_width push!(result, wrapline(line, line_width)) else push!(result, line) end end return strip(join(result, "\n")) end function wrapline(text, line_width = 75) result = "" while length(text) > line_width result *= first(text, line_width) * "\n" text = chop(text, head = line_width, tail = 0) end result *= text end