Merge pull request #350 from JonasIsensee/texexports

Reworking format.jl
pull/360/head
Shuhei Kadowaki 2020-06-02 19:24:13 +09:00 committed by GitHub
commit 73bf7fe77f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 915 additions and 854 deletions

View File

@ -2,7 +2,6 @@ language: julia
julia:
- 1 # current stable
- 1.0 # lts
- nightly
script:

View File

@ -23,7 +23,7 @@ Mustache = "0.4.1, 0.5, 1"
Plots = "0.28, 0.29, 1.0"
Requires = "1.0"
YAML = "0.3, 0.4"
julia = "1"
julia = "1.2"
[extras]
Cairo = "159f3aea-2a34-519c-b102-8c37f9878175"

View File

@ -1,6 +1,6 @@
module Weave
using Highlights, Mustache, Requires
using Highlights, Mustache, Requires, Pkg
# directories
@ -16,16 +16,19 @@ const WEAVE_OPTION_NAME_DEPRECATED = "options" # remove this when tagging v0.11
const WEAVE_OPTION_DEPRECATE_ID = "weave_option_duplicate_id"
const DEFAULT_FIG_PATH = "figures"
const WEAVE_VERSION = try
'v' * Pkg.TOML.parsefile(normpath(PKG_DIR, "Project.toml"))["version"]
catch
""
end
weave_info() = WEAVE_VERSION, string(Date(now()))
function __init__()
@require Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" include("plots.jl")
@require Gadfly = "c91e804a-d5a3-530f-b6f0-dfbca275c004" include("gadfly.jl")
end
# utilitity functions
@static @isdefined(isnothing) || begin
isnothing(::Any) = false
isnothing(::Nothing) = true
end
take2string!(io) = String(take!(io))
"""
@ -33,7 +36,7 @@ take2string!(io) = String(take!(io))
List supported output formats
"""
list_out_formats(io = stdout) = for (k, v) in FORMATS; println(io, string(k, ": ", v.formatdict[:description])); end
list_out_formats(io = stdout) = for (k, v) in FORMATS; println(io, string(k, ": ", v.description)); end
"""
tangle(source::AbstractString; kwargs...)
@ -171,36 +174,40 @@ function weave(
throw_errors = throw_errors,
)
# format document
# render document
# ---------------
# overwrites options with those specified in header, that are needed for formatting document
# overwrites options with those specified in header, that are needed for rendering document
# NOTE: these YAML options can be given dynamically
if !isnothing(weave_options)
if haskey(weave_options, "template")
template = weave_options["template"]
# resolve relative to this document
# resolve relative to this document
template isa AbstractString && (template = normpath(dirname(source), template))
end
if haskey(weave_options, "css")
css = weave_options["css"]
# resolve relative to this document
# resolve relative to this document
css isa AbstractString && (css = normpath(dirname(source), css))
end
highlight_theme = get(weave_options, "highlight_theme", highlight_theme)
pandoc_options = get(weave_options, "pandoc_options", pandoc_options)
latex_cmd = get(weave_options, "latex_cmd", latex_cmd)
keep_unicode = get(weave_options, "keep_unicode", keep_unicode)
end
get!(doc.format.formatdict, :keep_unicode, keep_unicode)
rendered = format(doc, template, highlight_theme; css = css)
set_rendering_options!(doc; template = template, highlight_theme = highlight_theme, css = css, keep_unicode = keep_unicode)
rendered = render_doc(doc)
outname = get_outname(out_path, doc)
open(io->write(io,rendered), outname, "w")
# document generation via external programs
# -----------------------------------------
if !isnothing(weave_options)
pandoc_options = get(weave_options, "pandoc_options", pandoc_options)
end
doctype = doc.doctype
if doctype == "pandoc2html"
mdname = outname
@ -333,8 +340,7 @@ include("display_methods.jl")
include("reader/reader.jl")
include("run.jl")
include("cache.jl")
include("formats.jl")
include("format.jl")
include("rendering/rendering.jl")
include("pandoc.jl")
include("converter.jl")

View File

@ -4,7 +4,7 @@ using Markdown, .WeaveMarkdown
mutable struct Report <: AbstractDisplay
cwd::AbstractString
basename::AbstractString
formatdict::Dict{Symbol,Any}
format::WeaveFormat
pending_code::AbstractString
cur_result::AbstractString
rich_output::AbstractString
@ -18,11 +18,11 @@ mutable struct Report <: AbstractDisplay
throw_errors::Bool
end
function Report(cwd, basename, formatdict, mimetypes, throw_errors)
function Report(cwd, basename, format, mimetypes, throw_errors)
Report(
cwd,
basename,
formatdict,
format,
"",
"",
"",

View File

@ -1,334 +0,0 @@
# TODO: reorganize this file into multiple files corresponding to each output format
using Mustache, Highlights, .WeaveMarkdown, Markdown, Dates, Pkg
using REPL.REPLCompletions: latex_symbols
function format(doc, template = nothing, highlight_theme = nothing; css = nothing)
docformat = doc.format
# Complete format dictionaries with defaults
get!(docformat.formatdict, :termstart, docformat.formatdict[:codestart])
get!(docformat.formatdict, :termend, docformat.formatdict[:codeend])
get!(docformat.formatdict, :out_width, nothing)
get!(docformat.formatdict, :out_height, nothing)
get!(docformat.formatdict, :fig_pos, nothing)
get!(docformat.formatdict, :fig_env, nothing)
docformat.formatdict[:highlight_theme] = get_highlight_theme(highlight_theme)
restore_header!(doc)
lines = map(copy(doc.chunks)) do chunk
format_chunk(chunk, docformat)
end
body = join(lines, '\n')
return docformat isa JMarkdown2HTML ? render2html(body, doc, template, css, highlight_theme) :
docformat isa JMarkdown2tex ? render2tex(body, doc, template, highlight_theme) :
body
end
function render2html(body, doc, template, css, highlight_theme)
_, weave_source = splitdir(abspath(doc.source))
weave_version, weave_date = weave_info()
return Mustache.render(
get_template(template, false);
body = body,
stylesheet = get_stylesheet(css),
highlight_stylesheet = get_highlight_stylesheet(MIME("text/html"), highlight_theme),
header_script = doc.header_script,
weave_source = weave_source,
weave_version = weave_version,
weave_date = weave_date,
[Pair(Symbol(k), v) for (k, v) in doc.header]...,
)
end
function render2tex(body, doc, template, highlight_theme)
return Mustache.render(
get_template(template, true);
body = body,
highlight = get_highlight_stylesheet(MIME("text/latex"), highlight_theme),
[Pair(Symbol(k), v) for (k, v) in doc.header]...,
)
end
get_highlight_theme(::Nothing) = Highlights.Themes.DefaultTheme
get_highlight_theme(highlight_theme::Type{<:Highlights.AbstractTheme}) = highlight_theme
get_template(::Nothing, tex::Bool = false) =
Mustache.template_from_file(normpath(TEMPLATE_DIR, tex ? "md2pdf.tpl" : "md2html.tpl"))
get_template(path::AbstractString, tex) = Mustache.template_from_file(path)
get_template(tpl::Mustache.MustacheTokens, tex) = tpl
get_stylesheet(::Nothing) = get_stylesheet(normpath(STYLESHEET_DIR, "skeleton.css"))
get_stylesheet(path::AbstractString) = read(path, String)
get_highlight_stylesheet(mime, highlight_theme) =
get_highlight_stylesheet(mime, get_highlight_theme(highlight_theme))
get_highlight_stylesheet(mime, highlight_theme::Type{<:Highlights.AbstractTheme}) =
sprint((io, x) -> Highlights.stylesheet(io, mime, x), highlight_theme)
const WEAVE_VERSION = try
'v' * Pkg.TOML.parsefile(normpath(PKG_DIR, "Project.toml"))["version"]
catch
""
end
weave_info() = WEAVE_VERSION, string(Date(now()))
# TODO: is there any other format where we want to restore headers ?
const HEADER_PRESERVE_DOCTYPES = ("github", "hugo")
function restore_header!(doc)
doc.doctype in HEADER_PRESERVE_DOCTYPES || return # don't restore
# only strips Weave headers
delete!(doc.header, WEAVE_OPTION_NAME)
if haskey(doc.header, WEAVE_OPTION_NAME_DEPRECATED)
@warn "Weave: `options` key is deprecated. Use `weave_options` key instead." _id = WEAVE_OPTION_DEPRECATE_ID maxlog = 1
delete!(doc.header, WEAVE_OPTION_NAME_DEPRECATED)
end
isempty(doc.header) && return
# restore remained headers as `DocChunk`
header_text = "---\n$(YAML.write(doc.header))---"
pushfirst!(doc.chunks, DocChunk(header_text, 0, 0))
end
format_chunk(chunk::DocChunk, docformat) = join((format_inline(c) for c in chunk.content))
format_inline(inline::InlineText) = inline.content
function format_inline(inline::InlineCode)
isempty(inline.rich_output) || return inline.rich_output
isempty(inline.figures) || return inline.figures[end]
return inline.output
end
function format_chunk(chunk::DocChunk, docformat::JMarkdown2tex)
out = IOBuffer()
io = IOBuffer()
for inline in chunk.content
if isa(inline, InlineText)
write(io, inline.content)
elseif !isempty(inline.rich_output)
clear_buffer_and_format!(io, out, WeaveMarkdown.latex)
write(out, addlines(inline.rich_output, inline))
elseif !isempty(inline.figures)
write(io, inline.figures[end], inline)
elseif !isempty(inline.output)
write(io, addlines(inline.output, inline))
end
end
clear_buffer_and_format!(io, out, WeaveMarkdown.latex)
out = take2string!(out)
return docformat.formatdict[:keep_unicode] ? out : uc2tex(out)
end
function format_chunk(chunk::DocChunk, docformat::JMarkdown2HTML)
out = IOBuffer()
io = IOBuffer()
for inline in chunk.content
if isa(inline, InlineText)
write(io, inline.content)
elseif !isempty(inline.rich_output)
clear_buffer_and_format!(io, out, WeaveMarkdown.html)
write(out, addlines(inline.rich_output, inline))
elseif !isempty(inline.figures)
write(io, inline.figures[end])
elseif !isempty(inline.output)
write(io, addlines(inline.output, inline))
end
end
clear_buffer_and_format!(io, out, WeaveMarkdown.html)
return take2string!(out)
end
function clear_buffer_and_format!(io::IOBuffer, out::IOBuffer, render_function)
text = take2string!(io)
m = Markdown.parse(text, flavor = WeaveMarkdown.weavemd)
write(out, string(render_function(m)))
end
addlines(op, inline) = inline.ctype === :line ? string('\n', op, '\n') : op
function format_chunk(chunk::CodeChunk, docformat)
formatdict = docformat.formatdict
# Fill undefined options with format specific defaults
isnothing(chunk.options[:out_width]) && (chunk.options[:out_width] = formatdict[:out_width])
isnothing(chunk.options[:fig_pos]) && (chunk.options[:fig_pos] = formatdict[:fig_pos])
# Only use floats if chunk has caption or sets fig_env
if !isnothing(chunk.options[:fig_cap]) && isnothing(chunk.options[:fig_env])
(chunk.options[:fig_env] = formatdict[:fig_env])
end
haskey(formatdict, :indent) && (chunk.content = indent(chunk.content, formatdict[:indent]))
chunk.content = format_code(chunk.content, docformat)
if !chunk.options[:eval]
return if chunk.options[:echo]
string(formatdict[:codestart], '\n', chunk.content, formatdict[:codeend])
else
""
end
end
if chunk.options[:term]
result = format_termchunk(chunk, docformat)
else
result = if chunk.options[:echo]
# Convert to output format and highlight (html, tex...) if needed
string(formatdict[:codestart], chunk.content, formatdict[:codeend], '\n')
else
""
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
format_output(result, docformat) = result
format_output(result, docformat::JMarkdown2HTML) = Markdown.htmlesc(result)
function format_output(result, 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
format_code(code, docformat) = code
# return "\\begin{minted}[mathescape, fontsize=\\small, xleftmargin=0.5em]{julia}\n$result\n\\end{minted}\n"
function format_code(code, docformat::JMarkdown2tex)
ret = highlight_code(MIME("text/latex"), code, docformat.formatdict[:highlight_theme])
docformat.formatdict[:keep_unicode] || return uc2tex(ret)
return ret
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)
return if occursin(r"^\\bf[A-Z]$", s)
replace(s, "\\bf" => "\\bm{\\mathrm{") * "}}"
elseif startswith(s, "\\bfrak")
replace(s, "\\bfrak" => "\\bm{\\mathfrak{") * "}}"
elseif startswith(s, "\\bf")
replace(s, "\\bf" => "\\bm{\\") * "}"
elseif startswith(s, "\\frak")
replace(s, "\\frak" => "\\mathfrak{") * "}"
else
s
end
end
format_code(code, docformat::JMarkdown2HTML) =
highlight_code(MIME("text/html"), code, docformat.formatdict[:highlight_theme])
format_code(code, docformat::Pandoc2HTML) =
highlight_code(MIME("text/html"), code, docformat.formatdict[:highlight_theme])
function format_termchunk(chunk, docformat)
return if should_render(chunk)
fd = docformat.formatdict
string(fd[:termstart], chunk.output, '\n', fd[:termend], '\n')
else
""
end
end
format_termchunk(chunk, docformat::JMarkdown2HTML) =
should_render(chunk) ? highlight_term(MIME("text/html"), chunk.output, docformat.formatdict[:highlight_theme]) : ""
format_termchunk(chunk, docformat::Pandoc2HTML) =
should_render(chunk) ? highlight_term(MIME("text/html"), chunk.output, docformat.formatdict[:highlight_theme]) : ""
# return "\\begin{minted}[mathescape, fontsize=\\small, xleftmargin=0.5em]{julia}\n$result\n\\end{minted}\n"
format_termchunk(chunk, docformat::JMarkdown2tex) =
should_render(chunk) ? highlight_term(MIME("text/latex"), chunk.output, docformat.formatdict[:highlight_theme]) : ""
should_render(chunk) = chunk.options[:echo] && chunk.options[:results] "hidden"
highlight_code(mime, code, highlight_theme) =
highlight(mime, strip(code), Highlights.Lexers.JuliaLexer, highlight_theme)
highlight_term(mime, output, highlight_theme) =
highlight(mime, strip(output), Highlights.Lexers.JuliaConsoleLexer, highlight_theme)
highlight(mime, output, lexer, theme = Highlights.Themes.DefaultTheme) =
sprint((io, x) -> Highlights.highlight(io, mime, x, lexer, theme), output)
indent(text, nindent) = join(map(x -> string(repeat(' ', nindent), x), split(text, '\n')), '\n')
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

View File

@ -1,481 +0,0 @@
# TODO:
# - 1. do assertions for definition mandatory fields in `@define_format` macro
# - 2. implement fallback format/rendering functions in format.jl
# - 3. export this as public API
abstract type WeaveFormat end
const FORMATS = Dict{String,WeaveFormat}()
macro define_format(type_name, supertype = :WeaveFormat)
return quote
@assert $(supertype) <: WeaveFormat "$($(supertype)) should be subtype of WeaveFormat"
struct $(type_name) <: $(supertype)
formatdict::Dict{Symbol,Any}
end
end
end
# TODO: do some assertion for necessary fields of `formatdict`
register_format!(format_name::AbstractString, format::WeaveFormat) = push!(FORMATS, format_name => format)
# HTML
# ----
@define_format JMarkdown2HTML
register_format!("md2html", JMarkdown2HTML(Dict(
:description => "Julia markdown to html",
:codestart => "\n",
:codeend => "\n",
:outputstart => "<pre class=\"output\">",
:outputend => "</pre>\n",
:fig_ext => ".png",
:mimetypes => [
"image/png",
"image/jpg",
"image/svg+xml",
"text/html",
"text/markdown",
"text/plain",
],
:extension => "html",
)))
@define_format Pandoc2HTML
register_format!("pandoc2html", Pandoc2HTML(Dict(
:description => "Markdown to HTML (requires Pandoc 2)",
:codestart => "\n",
:codeend => "\n",
:outputstart => "\n",
:outputend => "\n",
:fig_ext => ".png",
:extension => "md",
:mimetypes => [
"image/png",
"image/svg+xml",
"image/jpg",
"text/html",
"text/markdown",
"text/plain",
],
)))
# PDF and Tex
# -----------
@define_format JMarkdown2tex
let t = JMarkdown2tex(Dict(
:description => "Julia markdown to latex",
:codestart => "",
:codeend => "",
:outputstart => "\\begin{lstlisting}",
:outputend => "\\end{lstlisting}\n",
:fig_ext => ".pdf",
:extension => "tex",
:out_width => "\\linewidth",
:mimetypes => [
"application/pdf",
"image/png",
"image/jpg",
"text/latex",
"text/markdown",
"text/plain",
],
:keep_unicode => false,
))
register_format!("md2pdf", t)
register_format!("md2tex", t)
end
@define_format Tex
register_format!("tex", Tex(Dict(
:description => "Latex with custom code environments",
:codestart => "\\begin{juliacode}",
:codeend => "\\end{juliacode}",
:outputstart => "\\begin{juliaout}",
:outputend => "\\end{juliaout}",
:termstart => "\\begin{juliaterm}",
:termend => "\\end{juliaterm}",
:fig_ext => ".pdf",
:extension => "tex",
:out_width => "\\linewidth",
:fig_env => "figure",
:fig_pos => "htpb",
:mimetypes => ["application/pdf", "image/png", "text/latex", "text/plain"],
:keep_unicode => false,
)))
register_format!("texminted", Tex(Dict(
:description => "Latex using minted for highlighting",
:codestart =>
"\\begin{minted}[mathescape, fontsize=\\small, xleftmargin=0.5em]{julia}",
:codeend => "\\end{minted}",
:outputstart =>
"\\begin{minted}[fontsize=\\small, xleftmargin=0.5em, mathescape, frame = leftline]{text}",
:outputend => "\\end{minted}",
:termstart =>
"\\begin{minted}[fontsize=\\footnotesize, xleftmargin=0.5em, mathescape]{jlcon}",
:termend => "\\end{minted}",
:fig_ext => ".pdf",
:extension => "tex",
:out_width => "\\linewidth",
:fig_env => "figure",
:fig_pos => "htpb",
:mimetypes => ["application/pdf", "image/png", "text/latex", "text/plain"],
:keep_unicode => false,
)))
# pandoc
# ------
@define_format Pandoc
let p = Pandoc(Dict(
:description => "Pandoc markdown",
:codestart => "~~~~{.julia}",
:codeend => "~~~~~~~~~~~~~\n\n",
:outputstart => "~~~~",
:outputend => "~~~~\n\n",
:fig_ext => ".png",
:out_width => nothing,
:extension => "md",
# Prefer png figures for markdown conversion, svg doesn't work with latex
:mimetypes =>
["image/png", "image/jpg", "image/svg+xml", "text/markdown", "text/plain"],
))
register_format!("pandoc", p)
register_format!("pandoc2pdf", p)
end
# markdown
# --------
@define_format GitHubMarkdown
register_format!("github", GitHubMarkdown(Dict(
:description => "GitHub markdown",
:codestart => "````julia",
:codeend => "````\n\n",
:outputstart => "````",
:outputend => "````\n\n",
:fig_ext => ".png",
:extension => "md",
:mimetypes =>
["image/png", "image/svg+xml", "image/jpg", "text/markdown", "text/plain"],
)))
@define_format Hugo
register_format!("hugo", Hugo(Dict(
:description => "Hugo markdown (using shortcodes)",
:codestart => "````julia",
:codeend => "````\n\n",
:outputstart => "````",
:outputend => "````\n\n",
:fig_ext => ".png",
:extension => "md",
:uglyURLs => false, # if `false`, prepend figure path by `..`
)))
@define_format MultiMarkdown
register_format!("multimarkdown", MultiMarkdown(Dict(
:description => "MultiMarkdown",
:codestart => "````julia",
:codeend => "````\n\n",
:outputstart => "````",
:outputend => "````\n\n",
:fig_ext => ".png",
:extension => "md",
)))
# Rest
# ----
@define_format Rest
register_format!("rst", Rest(Dict(
:description => "reStructuredText and Sphinx",
:codestart => ".. code-block:: julia\n",
:codeend => "\n\n",
:outputstart => "::\n",
:outputend => "\n\n",
:indent => 4,
:fig_ext => ".png",
:extension => "rst",
:out_width => "15 cm",
)))
# Ansii
# -----
# asciidoc -b html5 -a source-highlighter=pygments ...
@define_format AsciiDoc
register_format!("asciidoc", AsciiDoc(Dict(
:description => "AsciiDoc",
:codestart => "[source,julia]\n--------------------------------------",
:codeend => "--------------------------------------\n\n",
:outputstart => "--------------------------------------",
:outputend => "--------------------------------------\n\n",
:fig_ext => ".png",
:extension => "txt",
:out_width => "600",
)))
# TODO: move this functions where used
# ------------------------------------
using Printf
function md_length_to_latex(def, reference)
if occursin("%", def)
_def = tryparse(Float64, replace(def, "%" => ""))
_def == nothing && return def
perc = round(_def / 100, digits = 2)
return "$perc$reference"
end
return def
end
function formatfigures(chunk, docformat::JMarkdown2HTML)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
width = chunk.options[:out_width]
height = chunk.options[:out_height]
f_pos = chunk.options[:fig_pos]
f_env = chunk.options[:fig_env]
result = ""
figstring = ""
# Set size
attribs = ""
width == nothing || (attribs = "width=\"$width\"")
(attribs != "" && height != nothing) && (attribs *= ",")
height == nothing || (attribs *= " height=\"$height\" ")
if caption != nothing
result *= """<figure>\n"""
end
for fig in fignames
figstring *= """<img src="$fig" $attribs />\n"""
end
result *= figstring
if caption != nothing
result *= """
<figcaption>$caption</figcaption>
"""
end
if caption != nothing
result *= "</figure>\n"
end
return result
end
function formatfigures(chunk, docformat::Union{Tex,JMarkdown2tex})
fignames = chunk.figures
caption = chunk.options[:fig_cap]
width = chunk.options[:out_width]
height = chunk.options[:out_height]
f_pos = chunk.options[:fig_pos]
f_env = chunk.options[:fig_env]
result = ""
figstring = ""
if f_env == nothing && caption != nothing
f_env = "figure"
end
(f_pos == nothing) && (f_pos = "!h")
# Set size
attribs = ""
width == nothing || (attribs = "width=$(md_length_to_latex(width,"\\linewidth"))")
(attribs != "" && height != nothing) && (attribs *= ",")
height == nothing || (attribs *= "height=$(md_length_to_latex(height,"\\paperheight"))")
if f_env != nothing
result *= "\\begin{$f_env}"
(f_pos != "") && (result *= "[$f_pos]")
result *= "\n"
end
for fig in fignames
if splitext(fig)[2] == ".tex" # Tikz figures
figstring *= "\\resizebox{$width}{!}{\\input{$fig}}\n"
else
if isempty(attribs)
figstring *= "\\includegraphics{$fig}\n"
else
figstring *= "\\includegraphics[$attribs]{$fig}\n"
end
end
end
# Figure environment
if caption != nothing
result *= string("\\center\n", "$figstring", "\\caption{$caption}\n")
else
result *= figstring
end
if chunk.options[:label] != nothing && f_env != nothing
label = chunk.options[:label]
result *= "\\label{fig:$label}\n"
end
if f_env != nothing
result *= "\\end{$f_env}\n"
end
return result
end
formatfigures(chunk, docformat::Pandoc2HTML) = formatfigures(chunk, pandoc)
function formatfigures(chunk, docformat::Pandoc)
fignames = chunk.figures
length(fignames) > 0 || (return "")
caption = chunk.options[:fig_cap]
label = get(chunk.options, :label, nothing)
result = ""
figstring = ""
attribs = ""
width = chunk.options[:out_width]
height = chunk.options[:out_height]
# Build figure attibutes
attribs = String[]
width == nothing || push!(attribs, "width=$width")
height == nothing || push!(attribs, "height=$height")
label == nothing || push!(attribs, "#fig:$label")
attribs = isempty(attribs) ? "" : "{" * join(attribs, " ") * "}"
if caption != nothing
result *= "![$caption]($(fignames[1]))$attribs\n"
for fig in fignames[2:end]
result *= "![]($fig)$attribs\n"
println("Warning, only the first figure gets a caption\n")
end
else
for fig in fignames
result *= "![]($fig)$attribs\\ \n\n"
end
end
return result
end
function formatfigures(chunk, docformat::GitHubMarkdown)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
result = ""
figstring = ""
length(fignames) > 0 || (return "")
if caption != nothing
result *= "![$caption]($(fignames[1]))\n"
for fig in fignames[2:end]
result *= "![]($fig)\n"
println("Warning, only the first figure gets a caption\n")
end
else
for fig in fignames
result *= "![]($fig)\n"
end
end
return result
end
function formatfigures(chunk, docformat::Hugo)
relpath = docformat.formatdict[:uglyURLs] ? "" : ".."
function format_shortcode(index_and_fig)
index, fig = index_and_fig
if index > 1
@warn("Only the first figure gets a caption.")
title_spec = ""
else
caption = chunk.options[:fig_cap]
title_spec = caption == nothing ? "" : "title=\"$(caption)\" "
end
"{{< figure src=\"$(joinpath(relpath, fig))\" $(title_spec) >}}"
end
mapreduce(format_shortcode, *, enumerate(chunk.figures), init = "")
end
function formatfigures(chunk, docformat::MultiMarkdown)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
result = ""
figstring = ""
if chunk.options[:out_width] == nothing
width = ""
else
width = "width=$(chunk.options[:out_width])"
end
length(fignames) > 0 || (return "")
if caption != nothing
result *= "![$caption][$(fignames[1])]\n\n"
result *= "[$(fignames[1])]: $(fignames[1]) $width\n"
for fig in fignames[2:end]
result *= "![][$fig]\n\n"
result *= "[$fig]: $fig $width\n"
println("Warning, only the first figure gets a caption\n")
end
else
for fig in fignames
result *= "![][$fig]\n\n"
result *= "[$fig]: $fig $width\n"
end
end
return result
end
function formatfigures(chunk, docformat::Rest)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
width = chunk.options[:out_width]
result = ""
figstring = ""
for fig in fignames
figstring *= @sprintf(".. image:: %s\n :width: %s\n\n", fig, width)
end
if caption != nothing
result *= string(
".. figure:: $(fignames[1])\n",
" :width: $width\n\n",
" $caption\n\n",
)
else
result *= figstring
return result
end
end
function formatfigures(chunk, docformat::AsciiDoc)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
width = chunk.options[:out_width]
result = ""
figstring = ""
for fig in fignames
figstring *= @sprintf("image::%s[width=%s]\n", fig, width)
end
if caption != nothing
result *= string("image::$(fignames[1])", "[width=$width,", "title=\"$caption\"]")
else
result *= figstring
return result
end
end

170
src/rendering/common.jl Normal file
View File

@ -0,0 +1,170 @@
# TODO: fix terminologies: `format_foo` -> `render_foo`
# fallback methods
# ----------------
set_rendering_options!(docformat::WeaveFormat; kwargs...) = return
function restore_header!(doc)
(hasproperty(doc.format, :restore_header) && doc.format.restore_header) || return
# only strips Weave headers
delete!(doc.header, WEAVE_OPTION_NAME)
if haskey(doc.header, WEAVE_OPTION_NAME_DEPRECATED)
@warn "Weave: `options` key is deprecated. Use `weave_options` key instead." _id = WEAVE_OPTION_DEPRECATE_ID maxlog = 1
delete!(doc.header, WEAVE_OPTION_NAME_DEPRECATED)
end
isempty(doc.header) && return
# restore remained headers as `DocChunk`
header_text = "---\n$(YAML.write(doc.header))---"
pushfirst!(doc.chunks, DocChunk(header_text, 0, 0))
end
format_chunk(chunk::DocChunk, docformat) = join((format_inline(c) for c in chunk.content))
format_inline(inline::InlineText) = inline.content
function format_inline(inline::InlineCode)
isempty(inline.rich_output) || return inline.rich_output
isempty(inline.figures) || return inline.figures[end]
return inline.output
end
function format_chunk(chunk::CodeChunk, docformat)
# Fill undefined options with format specific defaults
isnothing(chunk.options[:out_width]) && (chunk.options[:out_width] = docformat.out_width)
isnothing(chunk.options[:fig_pos]) && (chunk.options[:fig_pos] = docformat.fig_pos)
# Only use floats if chunk has caption or sets fig_env
if !isnothing(chunk.options[:fig_cap]) && isnothing(chunk.options[:fig_env])
(chunk.options[:fig_env] = docformat.fig_env)
end
hasproperty(docformat, :indent) && (chunk.content = indent(chunk.content, docformat.indent))
chunk.content = format_code(chunk.content, docformat)
if !chunk.options[:eval]
return if chunk.options[:echo]
string(docformat.codestart, '\n', chunk.content, docformat.codeend)
else
""
end
end
if chunk.options[:term]
result = format_termchunk(chunk, docformat)
else
result = if chunk.options[:echo]
# Convert to output format and highlight (html, tex...) if needed
string(docformat.codestart, chunk.content, docformat.codeend, '\n')
else
""
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
hasproperty(docformat, :indent) && (chunk.output = indent(chunk.output, docformat.indent))
strip(chunk.output) "" && (
result *= "$(docformat.outputstart)$(chunk.output)\n$(docformat.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
format_code(code, docformat) = code
indent(text, nindent) = join(map(x -> string(repeat(' ', nindent), x), split(text, '\n')), '\n')
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
format_output(result, docformat) = result
function format_termchunk(chunk, docformat)
return if should_render(chunk)
string(docformat.termstart, chunk.output, '\n', docformat.termend, '\n')
else
""
end
end
should_render(chunk) = chunk.options[:echo] && chunk.options[:results] "hidden"
render_doc(docformat, body, doc) = body
# utilities
# ---------
function clear_buffer_and_format!(io::IOBuffer, out::IOBuffer, render_function)
text = take2string!(io)
m = Markdown.parse(text, flavor = WeaveMarkdown.weavemd)
write(out, string(render_function(m)))
end
addlines(op, inline) = inline.ctype === :line ? string('\n', op, '\n') : op
get_template(path::AbstractString) = Mustache.template_from_file(path)
get_template(tpl::Mustache.MustacheTokens) = tpl
get_highlight_stylesheet(mime, highlight_theme) =
get_highlight_stylesheet(mime, get_highlight_theme(highlight_theme))
get_highlight_stylesheet(mime, highlight_theme::Type{<:Highlights.AbstractTheme}) =
sprint((io, x) -> Highlights.stylesheet(io, mime, x), highlight_theme)
get_highlight_theme(::Nothing) = Highlights.Themes.DefaultTheme
get_highlight_theme(highlight_theme::Type{<:Highlights.AbstractTheme}) = highlight_theme
highlight_code(mime, code, highlight_theme) =
highlight(mime, strip(code), Highlights.Lexers.JuliaLexer, highlight_theme)
highlight_term(mime, output, highlight_theme) =
highlight(mime, strip(output), Highlights.Lexers.JuliaConsoleLexer, highlight_theme)
highlight(mime, output, lexer, theme = Highlights.Themes.DefaultTheme) =
sprint((io, x) -> Highlights.highlight(io, mime, x, lexer, theme), output)

View File

@ -0,0 +1,150 @@
# HTML
# ----
abstract type HTMLFormat <: WeaveFormat end
format_code(code, docformat::HTMLFormat) =
highlight_code(MIME("text/html"), code, docformat.highlight_theme)
format_termchunk(chunk, docformat::HTMLFormat) =
should_render(chunk) ? highlight_term(MIME("text/html"), chunk.output, docformat.highlight_theme) : ""
# Julia markdown
# --------------
Base.@kwdef mutable struct JMarkdown2HTML <: HTMLFormat
description = "Julia markdown to html"
extension = "html"
codestart = "\n"
codeend = "\n"
termstart = codestart
termend = codeend
outputstart = "<pre class=\"output\">"
outputend = "</pre>\n"
mimetypes = ["image/png", "image/jpg", "image/svg+xml",
"text/html", "text/markdown", "text/plain"]
fig_ext = ".png"
out_width = nothing
out_height = nothing
fig_pos = nothing
fig_env = nothing
# specials
template = nothing
stylesheet = nothing
highlight_theme = nothing
end
register_format!("md2html", JMarkdown2HTML())
function set_rendering_options!(docformat::JMarkdown2HTML; template = nothing, css = nothing, highlight_theme = nothing, kwargs...)
docformat.template = get_html_template(template)
docformat.stylesheet = get_stylesheet(css)
docformat.highlight_theme = get_highlight_theme(highlight_theme)
end
get_html_template(::Nothing) = get_template(normpath(TEMPLATE_DIR, "md2html.tpl"))
get_html_template(x) = get_template(x)
get_stylesheet(::Nothing) = get_stylesheet(normpath(STYLESHEET_DIR, "skeleton.css"))
get_stylesheet(path::AbstractString) = read(path, String)
# very similar to tex version of function
function format_chunk(chunk::DocChunk, docformat::JMarkdown2HTML)
out = IOBuffer()
io = IOBuffer()
for inline in chunk.content
if isa(inline, InlineText)
write(io, inline.content)
elseif !isempty(inline.rich_output)
clear_buffer_and_format!(io, out, WeaveMarkdown.html)
write(out, addlines(inline.rich_output, inline))
elseif !isempty(inline.figures)
write(io, inline.figures[end])
elseif !isempty(inline.output)
write(io, addlines(inline.output, inline))
end
end
clear_buffer_and_format!(io, out, WeaveMarkdown.html)
return take2string!(out)
end
format_output(result, docformat::JMarkdown2HTML) = Markdown.htmlesc(result)
function formatfigures(chunk, docformat::JMarkdown2HTML)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
width = chunk.options[:out_width]
height = chunk.options[:out_height]
f_pos = chunk.options[:fig_pos]
f_env = chunk.options[:fig_env]
result = ""
figstring = ""
# Set size
attribs = ""
isnothing(width) || (attribs = "width=\"$width\"")
(!isempty(attribs) && !isnothing(height)) && (attribs *= ",")
isnothing(height) || (attribs *= " height=\"$height\" ")
if !isnothing(caption)
result *= """<figure>\n"""
end
for fig in fignames
figstring *= """<img src="$fig" $attribs />\n"""
end
result *= figstring
if !isnothing(caption)
result *= """
<figcaption>$caption</figcaption>
"""
end
if !isnothing(caption)
result *= "</figure>\n"
end
return result
end
function render_doc(docformat::JMarkdown2HTML, body, doc; css = nothing)
_, weave_source = splitdir(abspath(doc.source))
weave_version, weave_date = weave_info()
return Mustache.render(
docformat.template;
body = body,
stylesheet = docformat.stylesheet,
highlight_stylesheet = get_highlight_stylesheet(MIME("text/html"), docformat.highlight_theme),
header_script = doc.header_script,
weave_source = weave_source,
weave_version = weave_version,
weave_date = weave_date,
[Pair(Symbol(k), v) for (k, v) in doc.header]...,
)
end
# Pandoc
# ------
Base.@kwdef mutable struct Pandoc2HTML <: HTMLFormat
description = "Markdown to HTML (requires Pandoc 2)"
extension = "md"
codestart = "\n"
codeend = "\n"
termstart = codestart
termend = codeend
outputstart = "\n"
outputend = "\n"
mimetypes = ["image/png", "image/svg+xml", "image/jpg",
"text/html", "text/markdown", "text/plain"]
fig_ext = ".png"
out_width = nothing
out_height = nothing
fig_pos = nothing
fig_env = nothing
end
register_format!("pandoc2html", Pandoc2HTML())
formatfigures(chunk, docformat::Pandoc2HTML) = formatfigures(chunk, Pandoc())

View File

@ -0,0 +1,196 @@
abstract type MarkdownFormat <: WeaveFormat end
# GitHub markdown
# ---------------
Base.@kwdef mutable struct GitHubMarkdown <: MarkdownFormat
description = "GitHub markdown"
extension = "md"
codestart = "````julia"
codeend = "````\n\n"
termstart = codestart
termend = codeend
outputstart = "````"
outputend = "````\n\n"
fig_ext = ".png"
mimetypes = ["image/png", "image/svg+xml", "image/jpg",
"text/markdown", "text/plain"]
out_width = nothing
out_height = nothing
fig_pos = nothing
fig_env = nothing
# specials
preserve_header = true
end
register_format!("github", GitHubMarkdown())
function formatfigures(chunk, docformat::GitHubMarkdown)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
result = ""
figstring = ""
length(fignames) > 0 || (return "")
if !isnothing(caption)
result *= "![$caption]($(fignames[1]))\n"
for fig in fignames[2:end]
result *= "![]($fig)\n"
println("Warning, only the first figure gets a caption\n")
end
else
for fig in fignames
result *= "![]($fig)\n"
end
end
return result
end
# Hugo markdown
# -------------
Base.@kwdef mutable struct Hugo <: MarkdownFormat
description = "Hugo markdown (using shortcodes)"
extension = "md"
codestart = "````julia"
codeend = "````\n\n"
termstart = codestart
termend = codeend
outputstart = "````"
outputend = "````\n\n"
mimetypes = default_mime_types
fig_ext = ".png"
out_width = nothing
out_height = nothing
fig_pos = nothing
fig_env = nothing
# specials
preserve_header = true
uglyURLs = false # if `false`, prepend figure path by `..`
end
register_format!("hugo", Hugo())
function formatfigures(chunk, docformat::Hugo)
relpath = docformat.uglyURLs ? "" : ".."
mapreduce(*, enumerate(chunk.figures), init = "") do (index, fig)
if index > 1
@warn("Only the first figure gets a caption.")
title_spec = ""
else
caption = chunk.options[:fig_cap]
title_spec = isnothing(caption) ? "" : "title=\"$(caption)\" "
end
"{{< figure src=\"$(joinpath(relpath, fig))\" $(title_spec) >}}"
end
end
# multi language markdown
# -----------------------
Base.@kwdef mutable struct MultiMarkdown <: MarkdownFormat
description = "MultiMarkdown"
extension = "md"
codestart = "````julia"
codeend = "````\n\n"
termstart = codestart
termend = codeend
outputstart = "````"
outputend = "````\n\n"
mimetypes = default_mime_types
fig_ext = ".png"
out_width = nothing
out_height = nothing
fig_pos = nothing
fig_env = nothing
# specials
preserve_header = true
end
register_format!("multimarkdown", MultiMarkdown())
function formatfigures(chunk, docformat::MultiMarkdown)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
result = ""
figstring = ""
if chunk.options[:out_width] == nothing
width = ""
else
width = "width=$(chunk.options[:out_width])"
end
length(fignames) > 0 || (return "")
if !isnothing(caption)
result *= "![$caption][$(fignames[1])]\n\n"
result *= "[$(fignames[1])]: $(fignames[1]) $width\n"
for fig in fignames[2:end]
result *= "![][$fig]\n\n"
result *= "[$fig]: $fig $width\n"
println("Warning, only the first figure gets a caption\n")
end
else
for fig in fignames
result *= "![][$fig]\n\n"
result *= "[$fig]: $fig $width\n"
end
end
return result
end
# pandoc
# ------
Base.@kwdef mutable struct Pandoc <: MarkdownFormat
description = "Pandoc markdown"
extension = "md"
codestart = "~~~~{.julia}"
codeend = "~~~~~~~~~~~~~\n\n"
termstart = codestart
termend = codeend
outputstart = "~~~~"
outputend = "~~~~\n\n"
# Prefer png figures for markdown conversion, svg doesn't work with latex
mimetypes = ["image/png", "image/jpg", "image/svg+xml",
"text/markdown", "text/plain"]
fig_ext = ".png"
out_width = nothing
out_height = nothing
fig_pos = nothing
fig_env = nothing
end
register_format!("pandoc", Pandoc())
register_format!("pandoc2pdf", Pandoc())
function formatfigures(chunk, docformat::Pandoc)
fignames = chunk.figures
length(fignames) > 0 || (return "")
caption = chunk.options[:fig_cap]
label = get(chunk.options, :label, nothing)
result = ""
figstring = ""
attribs = ""
width = chunk.options[:out_width]
height = chunk.options[:out_height]
# Build figure attibutes
attribs = String[]
isnothing(width) || push!(attribs, "width=$width")
isnothing(height) || push!(attribs, "height=$height")
isnothing(label) || push!(attribs, "#fig:$label")
attribs = isempty(attribs) ? "" : "{" * join(attribs, " ") * "}"
if !isnothing(caption)
result *= "![$caption]($(fignames[1]))$attribs\n"
for fig in fignames[2:end]
result *= "![]($fig)$attribs\n"
println("Warning, only the first figure gets a caption\n")
end
else
for fig in fignames
result *= "![]($fig)$attribs\\ \n\n"
end
end
return result
end

View File

@ -0,0 +1,35 @@
# TODO:
# - 1. Improve argument handling
# - 2. Update code to use UnPack.jl to make it more readable
# - 3. Export new interface
# - 4. Document Interface
using Mustache, Highlights, .WeaveMarkdown, Markdown, Dates, Printf
using REPL.REPLCompletions: latex_symbols
const FORMATS = Dict{String,WeaveFormat}()
# TODO: do some assertion for necessary fields of `format`
register_format!(format_name::AbstractString, format::WeaveFormat) = push!(FORMATS, format_name => format)
register_format!(_, format) = error("Format needs to be a subtype of WeaveFormat.")
set_rendering_options!(doc; kwargs...) = set_rendering_options!(doc.format; kwargs...)
function render_doc(doc::WeaveDoc)
restore_header!(doc)
docformat = doc.format
lines = map(copy(doc.chunks)) do chunk
format_chunk(chunk, docformat)
end
body = join(lines, '\n')
return render_doc(docformat, body, doc)
end
include("common.jl")
include("htmlformats.jl")
include("texformats.jl")
include("variousformats.jl")
include("markdownformats.jl")

234
src/rendering/texformats.jl Normal file
View File

@ -0,0 +1,234 @@
# Tex
# ---
abstract type TexFormat <: WeaveFormat end
set_rendering_options!(docformat::TexFormat; keep_unicode = false, kwargs...) = docformat.keep_unicode |= keep_unicode
function formatfigures(chunk, docformat::TexFormat)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
width = chunk.options[:out_width]
height = chunk.options[:out_height]
f_pos = chunk.options[:fig_pos]
f_env = chunk.options[:fig_env]
result = ""
figstring = ""
if isnothing(f_env) && !isnothing(caption)
f_env = "figure"
end
(isnothing(f_pos)) && (f_pos = "!h")
# Set size
attribs = ""
isnothing(width) || (attribs = "width=$(md_length_to_latex(width,"\\linewidth"))")
(!isempty(attribs) && !isnothing(height)) && (attribs *= ",")
isnothing(height) || (attribs *= "height=$(md_length_to_latex(height,"\\paperheight"))")
if !isnothing(f_env)
result *= "\\begin{$f_env}"
(!isempty(f_pos)) && (result *= "[$f_pos]")
result *= "\n"
end
for fig in fignames
if splitext(fig)[2] == ".tex" # Tikz figures
figstring *= "\\resizebox{$width}{!}{\\input{$fig}}\n"
else
if isempty(attribs)
figstring *= "\\includegraphics{$fig}\n"
else
figstring *= "\\includegraphics[$attribs]{$fig}\n"
end
end
end
# Figure environment
if !isnothing(caption)
result *= string("\\center\n", "$figstring", "\\caption{$caption}\n")
else
result *= figstring
end
if !isnothing(chunk.options[:label]) && !isnothing(f_env)
label = chunk.options[:label]
result *= "\\label{fig:$label}\n"
end
if !isnothing(f_env)
result *= "\\end{$f_env}\n"
end
return result
end
function md_length_to_latex(def, reference)
if occursin("%", def)
_def = tryparse(Float64, replace(def, "%" => ""))
isnothing(_def) && return def
perc = round(_def / 100, digits = 2)
return "$perc$reference"
end
return def
end
# plain Tex
# ---------
Base.@kwdef mutable struct Tex <: TexFormat
description = "Latex with custom code environments"
extension = "tex"
codestart = "\\begin{juliacode}"
codeend = "\\end{juliacode}"
termstart = "\\begin{juliaterm}"
termend = "\\end{juliaterm}"
outputstart = "\\begin{juliaout}"
outputend = "\\end{juliaout}"
mimetypes = ["application/pdf", "image/png", "text/latex", "text/plain"]
fig_ext = ".pdf"
out_width = "\\linewidth"
out_height = nothing
fig_pos = "htpb"
fig_env = "figure"
# specials
keep_unicode = false
end
register_format!("tex", Tex())
# minted Tex
# ----------
Base.@kwdef mutable struct TexMinted <: TexFormat
description = "Latex using minted for highlighting"
extension = "tex"
codestart = "\\begin{minted}[mathescape, fontsize=\\small, xleftmargin=0.5em]{julia}"
codeend = "\\end{minted}"
termstart = "\\begin{minted}[fontsize=\\footnotesize, xleftmargin=0.5em, mathescape]{jlcon}"
termend = "\\end{minted}"
outputstart = "\\begin{minted}[fontsize=\\small, xleftmargin=0.5em, mathescape, frame = leftline]{text}"
outputend = "\\end{minted}"
mimetypes = ["application/pdf", "image/png", "text/latex", "text/plain"]
fig_ext = ".pdf"
out_width = "\\linewidth"
out_height = nothing
fig_pos = "htpb"
fig_env = "figure"
# specials
keep_unicode = false
end
register_format!("texminted", TexMinted())
# Tex (directly to PDF)
# ---------------------
Base.@kwdef mutable struct JMarkdown2tex <: TexFormat
description = "Julia markdown to latex"
extension = "tex"
codestart = ""
codeend = ""
termstart = codestart
termend = codeend
outputstart = "\\begin{lstlisting}"
outputend = "\\end{lstlisting}\n"
mimetypes = ["application/pdf", "image/png", "image/jpg",
"text/latex", "text/markdown", "text/plain"]
fig_ext = ".pdf"
out_width = "\\linewidth"
out_height = nothing
fig_pos = nothing
fig_env = nothing
# specials
highlight_theme = nothing
template = nothing
keep_unicode = false
end
register_format!("md2tex", JMarkdown2tex())
register_format!("md2pdf", JMarkdown2tex())
function set_rendering_options!(docformat::JMarkdown2tex; template = nothing, highlight_theme = nothing, keep_unicode = false, kwargs...)
docformat.template = get_tex_template(template)
docformat.highlight_theme = get_highlight_theme(highlight_theme)
docformat.keep_unicode |= keep_unicode
end
get_tex_template(::Nothing) = get_template(normpath(TEMPLATE_DIR, "md2pdf.tpl"))
get_tex_template(x) = get_template(x)
function render_doc(docformat::JMarkdown2tex, body, doc)
return Mustache.render(
docformat.template;
body = body,
highlight = get_highlight_stylesheet(MIME("text/latex"), docformat.highlight_theme),
[Pair(Symbol(k), v) for (k, v) in doc.header]...,
)
end
# very similar to export to html
function format_chunk(chunk::DocChunk, docformat::JMarkdown2tex)
out = IOBuffer()
io = IOBuffer()
for inline in chunk.content
if isa(inline, InlineText)
write(io, inline.content)
elseif !isempty(inline.rich_output)
clear_buffer_and_format!(io, out, WeaveMarkdown.latex)
write(out, addlines(inline.rich_output, inline))
elseif !isempty(inline.figures)
write(io, inline.figures[end], inline)
elseif !isempty(inline.output)
write(io, addlines(inline.output, inline))
end
end
clear_buffer_and_format!(io, out, WeaveMarkdown.latex)
out = take2string!(out)
return docformat.keep_unicode ? out : uc2tex(out)
end
function format_output(result, 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.keep_unicode || return uc2tex(result_escaped, true)
return result_escaped
end
function format_code(code, docformat::JMarkdown2tex)
ret = highlight_code(MIME("text/latex"), code, docformat.highlight_theme)
docformat.keep_unicode || return uc2tex(ret)
return ret
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
# should_render(chunk) ? highlight_term(MIME("text/latex"), , docformat.highlight_theme) : ""
format_termchunk(chunk, docformat::JMarkdown2tex) =
should_render(chunk) ? highlight_term(MIME("text/latex"), chunk.output, docformat.highlight_theme) : ""
# Make julia symbols (\bf* etc.) valid latex
function texify(s)
return if occursin(r"^\\bf[A-Z]$", s)
replace(s, "\\bf" => "\\bm{\\mathrm{") * "}}"
elseif startswith(s, "\\bfrak")
replace(s, "\\bfrak" => "\\bm{\\mathfrak{") * "}}"
elseif startswith(s, "\\bf")
replace(s, "\\bf" => "\\bm{\\") * "}"
elseif startswith(s, "\\frak")
replace(s, "\\frak" => "\\mathfrak{") * "}"
else
s
end
end

View File

@ -0,0 +1,86 @@
# Rest
# ----
Base.@kwdef mutable struct Rest <: WeaveFormat
description = "reStructuredText and Sphinx"
extension = "rst"
codestart = ".. code-block:: julia\n"
codeend = "\n\n"
termstart = codestart
termend = codeend
outputstart = "::\n"
outputend = "\n\n"
mimetypes = default_mime_types
fig_ext = ".png"
out_width = "15 cm"
out_height = nothing
fig_pos = nothing
fig_env = nothing
# specials
indent = 4
end
register_format!("rst", Rest())
function formatfigures(chunk, docformat::Rest)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
width = chunk.options[:out_width]
result = ""
figstring = ""
for fig in fignames
figstring *= @sprintf(".. image:: %s\n :width: %s\n\n", fig, width)
end
if !isnothing(caption)
result *= string(
".. figure:: $(fignames[1])\n",
" :width: $width\n\n",
" $caption\n\n",
)
else
result *= figstring
return result
end
end
# Ansii
# -----
# asciidoc -b html5 -a source-highlighter=pygments ...
Base.@kwdef mutable struct AsciiDoc <: WeaveFormat
description = "AsciiDoc"
extension = "txt"
codestart = "[source,julia]\n--------------------------------------"
codeend = "--------------------------------------\n\n"
termstart = codestart
termend = codeend
outputstart = "--------------------------------------"
outputend = "--------------------------------------\n\n"
mimetypes = default_mime_types
fig_ext = ".png"
out_width = "600"
out_height = nothing
fig_pos = nothing
fig_env = nothing
end
register_format!("asciidoc", AsciiDoc())
function formatfigures(chunk, docformat::AsciiDoc)
fignames = chunk.figures
caption = chunk.options[:fig_cap]
width = chunk.options[:out_width]
result = ""
figstring = ""
for fig in fignames
figstring *= @sprintf("image::%s[width=%s]\n", fig, width)
end
if !isnothing(caption)
result *= string("image::$(fignames[1])", "[width=$width,", "title=\"$caption\"]")
else
result *= figstring
return result
end
end

View File

@ -41,9 +41,9 @@ function run_doc(
isnothing(mod) && (mod = sandbox = Core.eval(Main, :(module $(gensym(:WeaveSandBox)) end))::Module)
@eval mod WEAVE_ARGS = $args
mimetypes = get(doc.format.formatdict, :mimetypes, default_mime_types)
mimetypes = doc.format.mimetypes
report = Report(doc.cwd, doc.basename, doc.format.formatdict, mimetypes, throw_errors)
report = Report(doc.cwd, doc.basename, doc.format, mimetypes, throw_errors)
pushdisplay(report)
try
if cache !== :off && cache !== :refresh
@ -260,8 +260,8 @@ function eval_chunk(chunk::CodeChunk, report::Report, SandBox::Module)
report.fignum = 1
report.cur_chunk = chunk
if haskey(report.formatdict, :out_width) && isnothing(chunk.options[:out_width])
chunk.options[:out_width] = report.formatdict[:out_width]
if hasproperty(report.format, :out_width) && isnothing(chunk.options[:out_width])
chunk.options[:out_width] = report.format.out_width
end
chunk.result = run_code(chunk, report, SandBox)
@ -342,13 +342,13 @@ end
"""Get output file name based on out_path"""
function get_outname(out_path::Symbol, doc::WeaveDoc; ext = nothing)
isnothing(ext) && (ext = doc.format.formatdict[:extension])
isnothing(ext) && (ext = doc.format.extension)
outname = "$(doc.cwd)/$(doc.basename).$ext"
end
"""Get output file name based on out_path"""
function get_outname(out_path::AbstractString, doc::WeaveDoc; ext = nothing)
isnothing(ext) && (ext = doc.format.formatdict[:extension])
isnothing(ext) && (ext = doc.format.extension)
splitted = splitext(out_path)
if (splitted[2]) == ""
outname = "$(doc.cwd)/$(doc.basename).$ext"
@ -358,9 +358,8 @@ function get_outname(out_path::AbstractString, doc::WeaveDoc; ext = nothing)
end
function set_rc_params(doc::WeaveDoc, fig_path, fig_ext)
formatdict = doc.format.formatdict
if isnothing(fig_ext)
doc.chunk_defaults[:fig_ext] = formatdict[:fig_ext]
doc.chunk_defaults[:fig_ext] = doc.format.fig_ext
else
doc.chunk_defaults[:fig_ext] = fig_ext
end
@ -393,7 +392,7 @@ function collect_results(chunk::CodeChunk, fmt::ScriptResult)
push!(result_chunks, rchunk)
end
end
if content != ""
if !isempty(content)
startswith(content, "\n") || (content = "\n" * content)
rchunk = CodeChunk(
content,
@ -430,7 +429,7 @@ function collect_results(chunk::CodeChunk, fmt::TermResult)
push!(result_chunks, rchunk)
end
end
if output != ""
if !isempty(output)
rchunk = CodeChunk(
"",
chunk.number,

View File

@ -2,6 +2,7 @@
abstract type WeaveChunk end
abstract type Inline end
abstract type WeaveFormat end
mutable struct WeaveDoc
source::AbstractString

View File

@ -7,6 +7,7 @@
\usepackage{graphicx}
\usepackage{microtype}
\usepackage{hyperref}
\setlength{\parindent}{0pt}
\setlength{\parskip}{1.2ex}

View File

@ -26,7 +26,7 @@ parsed = Weave.WeaveDoc("documents/chunk_options.noweb")
doc = run_doc(parsed, doctype = "md2html")
c_check = "<pre class='hljl'>\n<span class='hljl-n'>x</span><span class='hljl-t'> </span><span class='hljl-oB'>=</span><span class='hljl-t'> </span><span class='hljl-p'>[</span><span class='hljl-ni'>12</span><span class='hljl-p'>,</span><span class='hljl-t'> </span><span class='hljl-ni'>10</span><span class='hljl-p'>]</span><span class='hljl-t'>\n</span><span class='hljl-nf'>println</span><span class='hljl-p'>(</span><span class='hljl-n'>y</span><span class='hljl-p'>)</span>\n</pre>\n"
doc.format.formatdict[:highlight_theme] = DefaultTheme
doc.format.highlight_theme = DefaultTheme
c = Weave.format_code(doc.chunks[3].content, doc.format)
@test c_check == c
@ -39,7 +39,7 @@ parsed = Weave.WeaveDoc("documents/chunk_options.noweb")
doc = run_doc(parsed, doctype = "md2tex")
c_check = "\\begin{lstlisting}\n(*@\\HLJLnf{println}@*)(*@\\HLJLp{(}@*)(*@\\HLJLn{x}@*)(*@\\HLJLp{)}@*)\n\\end{lstlisting}\n"
doc.format.formatdict[:highlight_theme] = DefaultTheme
doc.format.highlight_theme = DefaultTheme
c = Weave.format_code(doc.chunks[4].content, doc.format)
@test c_check == c
@ -93,7 +93,7 @@ fmt = deepcopy(Weave.FORMATS["md2tex"])
f = Weave.format_chunk(chunk, fmt)
@test f == "\\section{Test chunk}\n\\ensuremath{\\alpha}\n\n"
fmt.formatdict[:keep_unicode] = true
fmt.keep_unicode = true
f = Weave.format_chunk(chunk, fmt)
@test f == "\\section{Test chunk}\nα\n\n"
@ -104,13 +104,14 @@ str = """
```
"""
doc = mock_doc(str; doctype = "md2tex")
doc = Weave.format(doc)
Weave.set_rendering_options!(doc.format)
doc = Weave.render_doc(doc)
@test occursin(Weave.uc2tex("α"), doc)
@test !occursin("α", doc)
doc = mock_doc(str; doctype = "md2tex")
doc.format.formatdict[:keep_unicode] = true
doc = Weave.format(doc)
Weave.set_rendering_options!(doc.format; keep_unicode = true)
doc = Weave.render_doc(doc)
@test occursin("α", doc)
@test !occursin(Weave.uc2tex("α"), doc)

View File

@ -1,8 +1,6 @@
using Weave: parse_options, parse_markdown
@static @isdefined(hasproperty) || (hasproperty(x, s::Symbol) = s in propertynames(x))
@testset "`parse_options`" begin
# general