mirror of https://github.com/mpastell/Weave.jl
129 lines
3.6 KiB
Julia
129 lines
3.6 KiB
Julia
using YAML
|
|
|
|
|
|
function WeaveDoc(source, informat = nothing)
|
|
path = abspath(source)
|
|
_, fname = splitdir(path)
|
|
basename = splitext(fname)[1]
|
|
|
|
isnothing(informat) && (informat = detect_informat(source))
|
|
header, chunks = parse_doc(read(source, String), informat)
|
|
|
|
# update default chunk options from header
|
|
chunk_defaults = deepcopy(get_chunk_defaults())
|
|
if (weave_options = get(header, WEAVE_OPTION_NAME, nothing)) !== nothing
|
|
for key in keys(chunk_defaults)
|
|
if (val = get(weave_options, string(key), nothing)) !== nothing
|
|
chunk_defaults[key] = val
|
|
end
|
|
end
|
|
end
|
|
if haskey(header, WEAVE_OPTION_NAME_DEPRECATED)
|
|
@warn "Weave: `options` key is deprecated. Use `weave_options` key instead." _id = WEAVE_OPTION_DEPRECATE_ID maxlog = 1
|
|
for key in keys(chunk_defaults)
|
|
if (val = get(header[WEAVE_OPTION_NAME_DEPRECATED], string(key), nothing)) !== nothing
|
|
chunk_defaults[key] = val
|
|
end
|
|
end
|
|
end
|
|
|
|
return WeaveDoc(
|
|
source,
|
|
basename,
|
|
path,
|
|
chunks,
|
|
"",
|
|
nothing,
|
|
"",
|
|
"",
|
|
header,
|
|
chunk_defaults,
|
|
)
|
|
end
|
|
|
|
"""
|
|
detect_informat(path)
|
|
|
|
Detect Weave input format based on file extension of `path`.
|
|
"""
|
|
function detect_informat(path)
|
|
ext = lowercase(last(splitext(path)))
|
|
|
|
ext == ".jl" && return "script"
|
|
ext == ".jmd" && return "markdown"
|
|
ext == ".ipynb" && return "notebook"
|
|
return "noweb"
|
|
end
|
|
|
|
function parse_doc(document, informat)
|
|
document = replace(document, "\r\n" => "\n") # normalize line ending
|
|
|
|
return informat == "markdown" ? parse_markdown(document) :
|
|
informat == "noweb" ? parse_markdown(document; is_pandoc = true) :
|
|
informat == "script" ? parse_script(document) :
|
|
informat == "notebook" ? parse_notebook(document) :
|
|
error("unsupported input format given: $informat")
|
|
end
|
|
|
|
# inline
|
|
# ------
|
|
|
|
function DocChunk(text::AbstractString, number, start_line; notebook = false)
|
|
# don't parse inline code in notebook
|
|
content = notebook ? parse_inline(text) : parse_inlines(text)
|
|
return DocChunk(content, number, start_line)
|
|
end
|
|
|
|
const INLINE_REGEX = r"`j\s+(.*?)`"
|
|
const INLINE_REGEXES = r"`j\s+(.*?)`|^!\s(.*)$"m
|
|
|
|
# handle code units correctly !
|
|
function parse_inlines(str)
|
|
ret = Inline[]
|
|
s = 1
|
|
code_no = text_no = 0
|
|
for m in eachmatch(INLINE_REGEXES, str)
|
|
e = m.offset
|
|
push!(ret, InlineText((str[s:prevind(str,e)]), text_no += 1))
|
|
i = findfirst(!isnothing, m.captures)
|
|
push!(ret, InlineCode(m.captures[i], code_no += 1, isone(i) ? :inline : :line))
|
|
s = e + ncodeunits(m.match)
|
|
end
|
|
push!(ret, InlineText(str[s:end], text_no += 1))
|
|
return ret
|
|
end
|
|
|
|
parse_inline(str) = Inline[InlineText(str, 1)]
|
|
|
|
# options
|
|
# -------
|
|
|
|
const OptionDict = Dict{Symbol,Any}
|
|
|
|
function parse_options(str)::OptionDict
|
|
str = string('(', str, ')')
|
|
ex = Meta.parse(str)
|
|
nt = if Meta.isexpr(ex, (
|
|
:block, # "(k1 = v1; k2 = v2, ...)"
|
|
:tuple, # "(k1 = v1, k2 = v2, ...)"
|
|
))
|
|
eval(Expr(:tuple, filter(is_valid_kv, ex.args)...))
|
|
elseif is_valid_kv(ex) # "(k = v)"
|
|
eval(Expr(:tuple, ex))
|
|
else
|
|
NamedTuple{}()
|
|
end
|
|
return dict(nt)
|
|
end
|
|
|
|
is_valid_kv(x) = Meta.isexpr(x, :(=))
|
|
dict(nt) = Dict((k => v for (k,v) in zip(keys(nt), values(nt)))...)
|
|
nt(dict) = NamedTuple{(Symbol.(keys(dict))...,)}((collect(values(dict))...,))
|
|
|
|
# each input format
|
|
# -----------------
|
|
|
|
include("markdown.jl")
|
|
include("script.jl")
|
|
include("notebook.jl")
|