Weave.jl/src/Weave.jl

353 lines
14 KiB
Julia
Raw Normal View History

module Weave
2020-05-09 17:01:41 +02:00
using Highlights, Mustache, Requires
2020-05-16 10:59:17 +02:00
const PKG_DIR = normpath(@__DIR__, "..")
const TEMPLATE_DIR = normpath(PKG_DIR, "templates")
const STYLESHEET_DIR = normpath(PKG_DIR, "stylesheets")
const WEAVE_OPTION_NAME = "weave_options"
const WEAVE_OPTION_NAME_DEPRECATED = "options" # remove this when tagging v0.11
2020-05-23 13:07:59 +02:00
const WEAVE_OPTION_DEPRECATE_ID = "weave_option_duplicate_id"
2020-05-18 15:59:18 +02:00
# keeps paths of sample documents for easy try
2020-05-23 12:42:19 +02:00
const EXAMPLE_FOLDER = normpath(PKG_DIR, "examples")
2020-05-18 15:59:18 +02:00
function __init__()
@require Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" include("plots.jl")
@require Gadfly = "c91e804a-d5a3-530f-b6f0-dfbca275c004" include("gadfly.jl")
end
2014-11-25 00:10:48 +01:00
2020-05-08 20:17:57 +02:00
@static @isdefined(isnothing) || begin
isnothing(::Any) = false
isnothing(::Nothing) = true
end
take2string!(io) = String(take!(io))
2020-05-08 20:17:57 +02:00
"""
2020-03-26 09:35:05 +01:00
list_out_formats()
List supported output formats
"""
2014-12-05 22:33:54 +01:00
function list_out_formats()
2020-05-08 16:39:17 +02:00
for format in keys(formats)
println(string(format, ": ", formats[format].description))
2020-05-06 16:37:04 +02:00
end
2014-12-05 22:33:54 +01:00
end
"""
2020-03-27 10:45:04 +01:00
tangle(source::AbstractString; kwargs...)
Tangle source code from input document to .jl file.
2015-01-01 22:36:58 +01:00
2020-03-27 10:45:04 +01:00
## Keyword options
2020-03-26 09:35:05 +01:00
- `informat::Union{Nothing,AbstractString} = nothing`: Input document format. By default (i.e. given `nothing`), Weave will set it automatically based on file extension. You can also specify either of `"script"`, `"markdown"`, `"notebook"`, or `"noweb"`
2020-03-27 10:45:04 +01:00
- `out_path::Union{Symbol,AbstractString} = :doc`: Path where the output is generated can be either of:
2020-03-26 09:35:05 +01:00
* `:doc`: Path of the source document (default)
* `:pwd`: Julia working directory
* `"somepath"`: `String` of output directory e.g. `"~/outdir"`, or of filename e.g. `"~/outdir/outfile.tex"`
"""
2020-03-26 09:35:05 +01:00
function tangle(
2020-03-26 17:49:48 +01:00
source::AbstractString;
2020-03-26 09:35:05 +01:00
out_path::Union{Symbol,AbstractString} = :doc,
informat::Union{Nothing,AbstractString} = nothing,
2020-05-08 16:39:17 +02:00
)
doc = WeaveDoc(source, informat)
2016-04-24 14:02:03 +02:00
doc.cwd = get_cwd(doc, out_path)
2015-01-01 22:36:58 +01:00
2016-04-24 14:02:03 +02:00
outname = get_outname(out_path, doc, ext = "jl")
2016-04-24 14:02:03 +02:00
open(outname, "w") do io
2020-05-06 16:37:04 +02:00
for chunk in doc.chunks
if typeof(chunk) == CodeChunk
options = merge(doc.chunk_defaults, chunk.options)
2020-05-08 16:39:17 +02:00
options[:tangle] && write(io, chunk.content * "\n")
2020-05-06 16:37:04 +02:00
end
end
end
2020-05-08 16:39:17 +02:00
doc.cwd == pwd() && (outname = basename(outname))
2020-05-06 16:37:04 +02:00
@info("Writing to file $outname")
end
"""
2020-03-27 10:45:04 +01:00
weave(source::AbstractString; kwargs...)
Weave an input document to output file.
2015-01-01 22:32:15 +01:00
2020-03-26 09:35:05 +01:00
## Keyword options
- `doctype::Union{Nothing,AbstractString} = nothing`: Output document format. By default (i.e. given `nothing`), Weave will set it automatically based on file extension. You can also manually specify it; see [`list_out_formats()`](@ref) for the supported formats
- `informat::Union{Nothing,AbstractString} = nothing`: Input document format. By default (i.e. given `nothing`), Weave will set it automatically based on file extension. You can also specify either of `"script"`, `"markdown"`, `"notebook"`, or `"noweb"`
2020-03-27 10:45:04 +01:00
- `out_path::Union{Symbol,AbstractString} = :doc`: Path where the output is generated can be either of:
2020-03-26 09:35:05 +01:00
* `:doc`: Path of the source document (default)
* `:pwd`: Julia working directory
* `"somepath"`: `String` of output directory e.g. `"~/outdir"`, or of filename e.g. `"~/outdir/outfile.tex"`
2020-03-27 10:45:04 +01:00
- `args::Dict = Dict()`: Arguments to be passed to the weaved document; will be available as `WEAVE_ARGS` in the document
2020-05-09 09:09:11 +02:00
- `mod::Union{Module,Nothing} = nothing`: Module where Weave `eval`s code. You can pass a `Module` object, otherwise create an new sandbox module.
2020-03-27 10:45:04 +01:00
- `fig_path::AbstractString = "figures"`: Where figures will be generated, relative to `out_path`
- `fig_ext::Union{Nothing,AbstractString} = nothing`: Extension for saved figures e.g. `".pdf"`, `".png"`. Default setting depends on `doctype`
- `cache_path::AbstractString = "cache"`: Where of cached output will be saved
- `cache::Symbol = :off`: Controls caching of code:
2020-03-26 09:35:05 +01:00
* `:off` means no caching (default)
* `:all` caches everything
* `:user` caches based on chunk options
* `:refresh` runs all code chunks and save new cache
2020-03-27 10:45:04 +01:00
- `throw_errors::Bool = false`: If `false` errors are included in output document and the whole document is executed. If `true` errors are thrown when they occur
- `template::Union{Nothing,AbstractString,Mustache.MustacheTokens} = nothing`: Template (file path) or `Mustache.MustacheTokens`s for `md2html` or `md2tex` formats
- `css::Union{Nothing,AbstractString} = nothing`: Path of a CSS file used for md2html format
- `highlight_theme::Union{Nothing,Type{<:Highlights.AbstractTheme}} = nothing`: Theme used for syntax highlighting (defaults to `Highlights.Themes.DefaultTheme`)
2020-03-27 10:45:04 +01:00
- `pandoc_options::Vector{<:AbstractString} = String[]`: `String`s of options to pass to pandoc for `pandoc2html` and `pandoc2pdf` formats, e.g. `["--toc", "-N"]`
- `latex_cmd::AbstractString = "xelatex"`: The command used to make PDF file from .tex
- `keep_unicode::Bool = false`: If `true`, do not convert unicode characters to their respective latex representation. This is especially useful if a font and tex-engine with support for unicode characters are used
2020-03-26 09:35:05 +01:00
!!! note
Run Weave from terminal and try to avoid weaving from IJulia or ESS; they tend to mess with capturing output.
"""
2020-03-26 09:35:05 +01:00
function weave(
source::AbstractString;
doctype::Union{Nothing,AbstractString} = nothing,
informat::Union{Nothing,AbstractString} = nothing,
2020-03-26 09:35:05 +01:00
out_path::Union{Symbol,AbstractString} = :doc,
args::Dict = Dict(),
2020-05-09 09:09:11 +02:00
mod::Union{Module,Nothing} = nothing,
2020-03-26 09:35:05 +01:00
fig_path::AbstractString = "figures",
fig_ext::Union{Nothing,AbstractString} = nothing,
cache_path::AbstractString = "cache",
cache::Symbol = :off,
throw_errors::Bool = false,
template::Union{Nothing,AbstractString,Mustache.MustacheTokens} = nothing,
css::Union{Nothing,AbstractString} = nothing, # TODO: rename to `stylesheet`
highlight_theme::Union{Nothing,Type{<:Highlights.AbstractTheme}} = nothing,
2020-03-26 09:35:05 +01:00
pandoc_options::Vector{<:AbstractString} = String[],
latex_cmd::AbstractString = "xelatex",
keep_unicode::Bool = false,
2020-03-26 09:35:05 +01:00
)
2020-05-16 16:10:44 +02:00
doc = WeaveDoc(source, informat)
2020-05-10 08:23:24 +02:00
# run document
# ------------
# overwrites options with those specified in header, that are needed for running document
# NOTE: these YAML options can NOT be given dynamically
weave_options = get(doc.header, WEAVE_OPTION_NAME, nothing)
if haskey(doc.header, WEAVE_OPTION_NAME_DEPRECATED)
2020-05-23 13:07:59 +02:00
@warn "Weave: `options` key is deprecated. Use `weave_options` key instead." _id = WEAVE_OPTION_DEPRECATE_ID maxlog = 1
weave_options = get(doc.header, WEAVE_OPTION_NAME_DEPRECATED, nothing)
end
if !isnothing(weave_options)
2020-05-10 08:23:24 +02:00
doctype = get(weave_options, "doctype", doctype)
specific_options!(weave_options, doctype)
2020-05-10 08:23:24 +02:00
if haskey(weave_options, "out_path")
out_path = let
out_path = weave_options["out_path"]
if out_path == ":doc" || out_path == ":pwd"
Symbol(out_path)
else
normpath(dirname(source), out_path) # resolve relative to this document
2020-05-10 08:23:24 +02:00
end
end
end
mod = get(weave_options, "mod", mod)
mod isa AbstractString && (mod = Main.eval(Meta.parse(mod)))
fig_path = get(weave_options, "fig_path", fig_path)
fig_ext = get(weave_options, "fig_ext", fig_ext)
cache_path = get(weave_options, "cache_path", cache_path)
cache = Symbol(get(weave_options, "cache", cache))
throw_errors = get(weave_options, "throw_errors", throw_errors)
end
doc = run_doc(
doc;
doctype = doctype,
mod = mod,
out_path = out_path,
args = args,
fig_path = fig_path,
fig_ext = fig_ext,
cache_path = cache_path,
cache = cache,
throw_errors = throw_errors,
)
# format document
# ---------------
# overwrites options with those specified in header, that are needed for formatting document
# NOTE: these YAML options can be given dynamically
if !isnothing(weave_options)
2020-05-10 08:23:24 +02:00
if haskey(weave_options, "template")
template = weave_options["template"]
# 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
css isa AbstractString && (css = normpath(dirname(source), css))
2020-05-10 08:23:24 +02:00
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)
2016-04-20 17:34:24 +02:00
2020-02-09 13:11:29 +01:00
outname = get_outname(out_path, doc)
open(io->write(io,rendered), outname, "w")
2014-12-03 14:41:53 +01:00
# document generation via external programs
doctype = doc.doctype
if doctype == "pandoc2html"
2020-02-09 13:11:29 +01:00
mdname = outname
outname = get_outname(out_path, doc, ext = "html")
pandoc2html(rendered, doc, highlight_theme, outname, pandoc_options)
2020-02-09 13:11:29 +01:00
rm(mdname)
elseif doctype == "pandoc2pdf"
2020-02-09 13:11:29 +01:00
mdname = outname
outname = get_outname(out_path, doc, ext = "pdf")
pandoc2pdf(rendered, doc, outname, pandoc_options)
2020-02-09 13:11:29 +01:00
rm(mdname)
elseif doctype == "md2pdf"
run_latex(doc, outname, latex_cmd)
2020-02-09 13:11:29 +01:00
outname = get_outname(out_path, doc, ext = "pdf")
end
2020-02-09 13:11:29 +01:00
doc.cwd == pwd() && (outname = basename(outname))
@info "Report weaved to $outname"
2020-02-09 13:11:29 +01:00
return abspath(outname)
2014-11-25 00:10:48 +01:00
end
2020-05-16 16:10:44 +02:00
weave(doc::AbstractString, doctype::Union{Symbol,AbstractString}; kwargs...) =
weave(doc; doctype = doctype, kwargs...)
function specific_options!(weave_options, doctype)
fmts = keys(formats)
for (k,v) in weave_options
if k in fmts
k == doctype && merge!(weave_options, v)
delete!(weave_options, k)
end
end
end
"""
2020-03-27 10:45:04 +01:00
notebook(source::AbstractString; kwargs...)
2020-03-26 09:35:05 +01:00
Convert Weave document `source` to Jupyter Notebook and execute the code
using [`nbconvert`](https://nbconvert.readthedocs.io/en/latest/).
2020-03-26 09:35:05 +01:00
**Ignores** all chunk options.
## Keyword options
2020-03-27 10:45:04 +01:00
- `out_path::Union{Symbol,AbstractString} = :pwd`: Path where the output is generated can be either of:
2020-03-26 09:35:05 +01:00
* `:doc`: Path of the source document
* `:pwd`: Julia working directory (default)
* `"somepath"`: `String` of output directory e.g. `"~/outdir"`, or of filename e.g. `"~/outdir/outfile.tex"`
- `timeout = -1`: nbconvert cell timeout in seconds. Defaults to `-1` (no timeout)
2020-03-27 10:45:04 +01:00
- `nbconvert_options::AbstractString = ""`: `String` of additional options to pass to nbconvert, such as `"--allow-errors"`
- `jupyter_path::AbstractString = "jupyter"`: Path/command for the Jupyter you want to use. Defaults to `"jupyter"`, which runs whatever is linked/alias to that
!!! warning
The code is _**not**_ executed by Weave, but by [`nbconvert`](https://nbconvert.readthedocs.io/en/latest/).
This means that the output doesn't necessarily always work properly; see [#116](https://github.com/mpastell/Weave.jl/issues/116).
!!! note
In order to _just_ convert Weave document to Jupyter Notebook,
use [`convert_doc`](@ref) instead.
"""
2020-03-26 09:35:05 +01:00
function notebook(
source::AbstractString;
out_path::Union{Symbol,AbstractString} = :pwd,
timeout = -1,
nbconvert_options::AbstractString = "",
jupyter_path::AbstractString = "jupyter",
)
doc = WeaveDoc(source)
2020-05-15 17:32:28 +02:00
converted = convert_to_notebook(doc)
2020-03-26 09:35:05 +01:00
doc.cwd = get_cwd(doc, out_path)
outfile = get_outname(out_path, doc, ext = "ipynb")
2020-03-26 09:35:05 +01:00
open(outfile, "w") do f
write(f, converted)
end
2020-03-27 10:45:04 +01:00
@info "Running nbconvert"
return read(
2020-03-26 09:35:05 +01:00
`$jupyter_path nbconvert --ExecutePreprocessor.timeout=$timeout --to notebook --execute $outfile $nbconvert_options --output $outfile`,
String,
)
end
2019-03-11 13:25:17 +01:00
"""
include_weave(source::AbstractString, informat::Union{Nothing,AbstractString} = nothing)
include_weave(m::Module, source::AbstractString, informat::Union{Nothing,AbstractString} = nothing)
2020-03-26 09:35:05 +01:00
Include code from Weave document calling `include_string` on all code from doc.
Code is run in the path of the include document.
"""
2020-03-26 09:35:05 +01:00
function include_weave(
m::Module,
source::AbstractString,
informat::Union{Nothing,AbstractString} = nothing,
2020-03-26 09:35:05 +01:00
)
2019-03-11 13:25:17 +01:00
old_path = pwd()
doc = WeaveDoc(source, informat)
2019-03-11 13:25:17 +01:00
cd(doc.path)
try
2020-05-08 16:39:17 +02:00
code = join(
[x.content for x in filter(x -> isa(x, Weave.CodeChunk), doc.chunks)],
"\n",
)
2019-03-11 13:25:17 +01:00
include_string(m, code)
catch err
throw(err)
2019-03-11 13:25:17 +01:00
finally
cd(old_path)
end
return nothing
end
include_weave(source, informat = nothing) = include_weave(Main, source, informat)
2019-03-11 13:25:17 +01:00
2020-05-08 16:39:17 +02:00
# Hooks to run before and after chunks, this is form IJulia,
# but note that Weave hooks take the chunk as input
const preexecute_hooks = Function[]
push_preexecute_hook(f::Function) = push!(preexecute_hooks, f)
2020-05-08 16:39:17 +02:00
pop_preexecute_hook(f::Function) =
splice!(preexecute_hooks, findfirst(x -> x == f, preexecute_hooks))
const postexecute_hooks = Function[]
push_postexecute_hook(f::Function) = push!(postexecute_hooks, f)
2020-05-08 16:39:17 +02:00
pop_postexecute_hook(f::Function) =
splice!(postexecute_hooks, findfirst(x -> x == f, postexecute_hooks))
2020-05-09 14:03:00 +02:00
include("types.jl")
include("config.jl")
include("WeaveMarkdown/markdown.jl")
include("display_methods.jl")
include("reader/reader.jl")
include("run.jl")
include("cache.jl")
include("formatters.jl")
include("format.jl")
include("pandoc.jl")
2020-05-15 17:38:18 +02:00
include("converter.jl")
2016-12-12 13:05:26 +01:00
2020-05-08 16:39:17 +02:00
export weave,
list_out_formats,
tangle,
convert_doc,
notebook,
2020-05-10 06:56:09 +02:00
set_chunk_defaults!,
2020-05-08 16:39:17 +02:00
get_chunk_defaults,
2020-05-10 06:56:09 +02:00
restore_chunk_defaults!,
2020-05-08 16:39:17 +02:00
include_weave
2014-11-25 00:10:48 +01:00
end