Merge pull request #80 from mpastell/inline_chunks

Support inline code
pull/71/merge
Matti Pastell 2016-12-29 16:44:36 +02:00 committed by GitHub
commit 090ad87ed9
11 changed files with 233 additions and 73 deletions

View File

@ -120,3 +120,20 @@ Weave will remove the first empty space from each line of documentation.
[See sample document:](https://github.com/mpastell/Weave.jl/blob/master/examples/FIR_design.jl)
## Inline code
You can also add inline code to your documents using
```
`j juliacode`
```
syntax. The code will be replaced with the output of running the code.
If the code produces figures the filename or base64 encoded string will be
added to output e.g. to include a Plots figure in markdown you can use:
```
![A plot](`j plot(1:10)`)
```

View File

@ -87,46 +87,49 @@ function weave(source ; doctype = :auto, plotlib=:auto,
css != nothing && (doc.css = css)
template != nothing && (doc.template = template)
try
doc = run(doc, doctype = doctype, plotlib=plotlib,
out_path=out_path, args = args,
fig_path = fig_path, fig_ext = fig_ext, cache_path = cache_path, cache=cache)
formatted = format(doc)
doc = run(doc, doctype = doctype, plotlib=plotlib,
out_path=out_path, args = args,
fig_path = fig_path, fig_ext = fig_ext, cache_path = cache_path, cache=cache)
formatted = format(doc)
outname = get_outname(out_path, doc)
outname = get_outname(out_path, doc)
open(outname, "w") do io
write(io, formatted)
end
open(outname, "w") do io
write(io, formatted)
#Special for that need external programs
if doc.doctype == "pandoc2html"
mdname = outname
outname = get_outname(out_path, doc, ext = "html")
pandoc2html(formatted, doc, outname)
rm(mdname)
elseif doc.doctype == "pandoc2pdf"
mdname = outname
outname = get_outname(out_path, doc, ext = "pdf")
pandoc2pdf(formatted, doc, outname)
rm(mdname)
elseif doc.doctype == "md2pdf"
success = run_latex(doc, outname, latex_cmd)
success || return
outname = get_outname(out_path, doc, ext = "pdf")
end
doc.cwd == pwd() && (outname = basename(outname))
info("Report weaved to $outname")
catch e
warn("Something went wrong during weaving")
print(e)
finally
doctype == :auto && (doctype = detect_doctype(doc.source))
if contains(doctype, "2pdf") && cache == :off
rm(doc.fig_path, force = true, recursive = true)
elseif contains(doctype, "2html")
rm(doc.fig_path, force = true, recursive = true)
end
end
#Special for that need external programs
if doc.doctype == "pandoc2html"
mdname = outname
outname = get_outname(out_path, doc, ext = "html")
pandoc2html(formatted, doc, outname)
rm(mdname)
elseif doc.doctype == "pandoc2pdf"
mdname = outname
outname = get_outname(out_path, doc, ext = "pdf")
pandoc2pdf(formatted, doc, outname)
rm(mdname)
elseif doc.doctype == "md2pdf"
success = run_latex(doc, outname, latex_cmd)
success || return
outname = get_outname(out_path, doc, ext = "pdf")
end
doctype == :auto && (doctype = detect_doctype(doc.source))
if contains(doctype, "2pdf") && cache == :off
rm(doc.fig_path, force = true, recursive = true)
elseif contains(doctype, "2html")
rm(doc.fig_path, force = true, recursive = true)
end
doc.cwd == pwd() && (outname = basename(outname))
info("Report weaved to $outname")
end
function weave(doc::AbstractString, doctype::AbstractString)

View File

@ -33,7 +33,28 @@ function restore_chunk(chunk::CodeChunk, cached)
return new_chunks
end
#Could be used to restore inline code in future
function restore_chunk(chunk::DocChunk, cached)
return chunk
#Restore inline code
function restore_chunk(chunk::DocChunk, cached::WeaveDoc)
#Get chunk from cached doc
c_chunk = filter(x -> x.number == chunk.number &&
isa(x, DocChunk), cached.chunks)
isempty(c_chunk) && return chunk
c_chunk = c_chunk[1]
#Collect cached code
c_inline = filter(x -> isa(x, InlineCode), c_chunk.content)
isempty(c_inline) && return chunk
#Restore cached results for Inline code
n = length(chunk.content)
for i in 1:n
if isa(chunk.content[i], InlineCode)
ci = filter(x -> x.number == chunk.content[i].number, c_inline)
isempty(ci) && continue
chunk.content[i].output = ci[1].output
chunk.content[i].rich_output = ci[1].rich_output
chunk.content[i].figures = ci[1].figures
end
end
return chunk
end

View File

@ -1,9 +1,12 @@
abstract WeaveChunk
abstract Inline
type WeaveDoc
source::AbstractString
basename::AbstractString
path::AbstractString
chunks::Array
chunks::Array{WeaveChunk}
cwd::AbstractString
format
doctype::AbstractString
@ -29,7 +32,7 @@ immutable ChunkOutput
figures::Array{AbstractString}
end
type CodeChunk
type CodeChunk <: WeaveChunk
content::AbstractString
number::Int
result_no::Int
@ -45,10 +48,34 @@ type CodeChunk
end
end
type DocChunk
content::AbstractString
type DocChunk <: WeaveChunk
content::Array{Inline}
number::Int
start_line::Int
function DocChunk(text::AbstractString, number::Int, start_line::Int, inline_regex = nothing)
chunks = parse_inline(text, inline_regex)
new(chunks, number, start_line)
end
end
type InlineText <: Inline
content::AbstractString
si::Int
ei::Int
number::Int
end
type InlineCode <: Inline
content::AbstractString
si::Int
ei::Int
number::Int
output::AbstractString
rich_output::AbstractString
figures::Array{AbstractString}
function InlineCode(content, si, ei, number)
new(content, si, ei, number, "", "", AbstractString[])
end
end
type TermResult

View File

@ -105,22 +105,32 @@ function get_titleblock(doc::WeaveDoc)
end
function strip_header(chunk::DocChunk)
if ismatch(r"^---$(?<header>.+)^---$"ms, chunk.content)
chunk.content = lstrip(replace(chunk.content, r"^---$(?<header>.+)^---$"ms, ""))
if ismatch(r"^---$(?<header>.+)^---$"ms, chunk.content[1].content)
chunk.content[1].content = lstrip(replace(chunk.content[1].content, r"^---$(?<header>.+)^---$"ms, ""))
end
return chunk
end
function format_chunk(chunk::DocChunk, formatdict, docformat)
return chunk.content
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 format_chunk(chunk::DocChunk, formatdict, docformat::JMarkdown2HTML)
m = Base.Markdown.parse(chunk.content)
text = format_chunk(chunk, formatdict, nothing)
m = Base.Markdown.parse(text)
return string(Documenter.Writers.HTMLWriter.mdconvert(m))
end
#Fixes to Base latex writer
function Base.Markdown.latex(io::IO, md::Base.Markdown.Paragraph)
println(io)
@ -144,7 +154,8 @@ end
function format_chunk(chunk::DocChunk, formatdict, docformat::JMarkdown2tex)
m = Base.Markdown.parse(chunk.content)
text = format_chunk(chunk, formatdict, nothing)
m = Base.Markdown.parse(text)
return Base.Markdown.latex(m)
end

View File

@ -5,6 +5,7 @@ pushopt(options::Dict,expr::Expr) = Base.Meta.isexpr(expr,:(=)) && (options[expr
type MarkupInput
codestart::Regex
codeend::Regex
inline::Regex
end
type ScriptInput
@ -12,23 +13,29 @@ type ScriptInput
doc_start::Regex
opt_line::Regex
opt_start::Regex
inline::Regex
end
type NotebookInput
inline
end
const input_formats = Dict{AbstractString, Any}(
"noweb" => MarkupInput(r"^<<(.*?)>>=\s*$",
r"^@\s*$"),
r"^@\s*$",
r"`j\s+(.*?)`"s
),
"markdown" => MarkupInput(
r"^[`~]{3,}(?:\{|\{\.|)julia(?:;|)\s*(.*?)(\}|\s*)$",
r"^[`~]{3,}\s*$"),
r"^[`~]{3,}\s*$",
r"`j\s+(.*?)`"s),
"script" => ScriptInput(
r"(^#'.*)|(^#%%.*)|(^# %%.*)",
r"(^#')|(^#%%)|(^# %%)",
r"(^#\+.*$)|(^#%%\+.*$)|(^# %%\+.*$)",
r"(^#\+)|(^#%%\+)|(^# %%\+)"),
"notebook" => NotebookInput()
r"(^#\+)|(^#%%\+)|(^# %%\+)",
r"`j\s+(.*?)`"s),
"notebook" => NotebookInput(nothing) #Don't parse inline code from notebooks
)
"""Detect the input format based on file extension"""
@ -59,7 +66,7 @@ function parse_header(chunk::CodeChunk)
end
function parse_header(chunk::DocChunk)
m = match(r"^---$(?<header>.+)^---$"ms, chunk.content)
m = match(r"^---$(?<header>.+)^---$"ms, chunk.content[1].content)
if m !== nothing
header = YAML.load(string(m[:header]))
else
@ -109,7 +116,7 @@ function parse_doc(document::AbstractString, format::MarkupInput)
haskey(options, :name) || (options[:name] = nothing)
if !isempty(strip(content))
chunk = DocChunk(content, docno, start_line)
chunk = DocChunk(content, docno, start_line, format.inline)
docno += 1
push!(parsed, chunk)
end
@ -143,7 +150,7 @@ function parse_doc(document::AbstractString, format::MarkupInput)
#Remember the last chunk
if strip(content) != ""
chunk = DocChunk(content, docno, start_line)
chunk = DocChunk(content, docno, start_line, format.inline)
#chunk = Dict{Symbol,Any}(:type => "doc", :content => content,
# :number => docno, :start_line => start_line)
push!(parsed, chunk)
@ -223,7 +230,7 @@ function parse_doc(document::AbstractString, format::ScriptInput)
elseif state == "doc" && strip(line) != "" && strip(read) != ""
state = "code"
(docno > 1) && (read = "\n" * read) # Add whitespace to doc chunk. Needed for markdown output
chunk = DocChunk(read, docno, start_line)
chunk = DocChunk(read, docno, start_line, format.inline)
push!(parsed, chunk)
options = Dict{Symbol,Any}()
start_line = lineno
@ -244,7 +251,7 @@ function parse_doc(document::AbstractString, format::ScriptInput)
chunk = CodeChunk("\n" * strip(read), codeno, start_line, optionString, options)
push!(parsed, chunk)
else
chunk = DocChunk(read, docno, start_line)
chunk = DocChunk(read, docno, start_line, format.inline)
push!(parsed, chunk)
end
@ -277,3 +284,32 @@ function parse_doc(document::String, format::NotebookInput)
return parsed
end
#Use this if regex is undefined
function parse_inline(text, noex)
return Inline[InlineText(text, 1, length(text), 1)]
end
function parse_inline(text::AbstractString, inline_ex::Regex)
ismatch(inline_ex, text) || return Inline[InlineText(text, 1, length(text), 1)]
inline_chunks = eachmatch(inline_ex, text)
s = 1
e = 1
res = Inline[]
textno = 1
codeno = 1
for ic in inline_chunks
s = ic.offset
doc = InlineText(text[e:(s-1)], e, s-1, textno)
textno += 1
push!(res, doc)
e = s + length(ic.match)
push!(res, InlineCode(ic.captures[1], s, e, codeno))
codeno += 1
end
push!(res, InlineText(text[e:end], e, length(text), textno))
return res
end

View File

@ -73,7 +73,7 @@ function Base.run(doc::WeaveDoc; doctype = :auto, plotlib=:auto,
for i = 1:n
chunk = doc.chunks[i]
if typeof(chunk) == CodeChunk
if isa(chunk, CodeChunk)
options = merge(rcParams[:chunk_defaults], chunk.options)
merge!(chunk.options, options)
end
@ -84,7 +84,7 @@ function Base.run(doc::WeaveDoc; doctype = :auto, plotlib=:auto,
result_chunks = restore_chunk(chunk, cached)
else
result_chunks = run_chunk(chunk, report, SandBox)
result_chunks = run_chunk(chunk, report, SandBox)
end
executed = [executed; result_chunks]
@ -119,6 +119,7 @@ end
function run_chunk(chunk::CodeChunk, report::Report, SandBox::Module)
info("Weaving chunk $(chunk.number) from line $(chunk.start_line)")
result_chunks = eval_chunk(chunk, report, SandBox)
contains(report.formatdict[:doctype], "2html") && (result_chunks = embed_figures(result_chunks, report.cwd))
return result_chunks
@ -155,9 +156,31 @@ function img2base64(fig, cwd)
end
function run_chunk(chunk::DocChunk, report::Report, SandBox::Module)
chunk.content = [run_inline(c, report, SandBox) for c in chunk.content]
return chunk
end
function run_inline(inline::InlineText, report::Report, SandBox::Module)
return inline
end
function run_inline(inline::InlineCode, report::Report, SandBox::Module)
#Make a temporary CodeChunk for running code. Collect results and don't wrap
chunk = CodeChunk(inline.content, 0, 0, "", Dict(:hold => true, :wrap => false))
options = merge(rcParams[:chunk_defaults], chunk.options)
merge!(chunk.options, options)
chunks = eval_chunk(chunk, report, SandBox)
contains(report.formatdict[:doctype], "2html") && (chunks = embed_figures(chunks, report.cwd))
output = chunks[1].output
startswith(output, "\n") && (output = replace(output, "\n", "", 1))
inline.output = output
inline.rich_output = chunks[1].rich_output
inline.figures = chunks[1].figures
return inline
end
function reset_report(report::Report)
report.cur_result = ""
report.figures = AbstractString[]
@ -231,8 +254,9 @@ end
#Parse chunk input to array of expressions
function parse_input(input::AbstractString)
parsed = Tuple{AbstractString, Any}[]
input = lstrip(input)
n = length(input)
pos = 2 #The first character is extra line end
pos = 1 #The first character is extra line end
while pos n
oldpos = pos
code, pos = parse(input, pos)
@ -243,7 +267,7 @@ end
function eval_chunk(chunk::CodeChunk, report::Report, SandBox::Module)
info("Weaving chunk $(chunk.number) from line $(chunk.start_line)")
if !chunk.options[:eval]
chunk.output = ""

View File

@ -80,14 +80,12 @@ function convert_doc(doc::WeaveDoc, format::NotebookOutput)
cells = []
ex_count = 1
doc.chunks[3].content
for chunk in doc.chunks
if typeof(chunk) == Weave.DocChunk
if isa(chunk, DocChunk)
push!(cells,
Dict("cell_type" => "markdown",
"metadata" => Dict(),
"source" => [strip(chunk.content)])
"source" => [strip(join([repr(c) for c in chunk.content], ""))])
)
else
push!(cells,
@ -111,8 +109,8 @@ end
function convert_doc(doc::WeaveDoc, format::MarkdownOutput)
output = ""
for chunk in doc.chunks
if typeof(chunk) == Weave.DocChunk
output *= chunk.content
if isa(chunk, DocChunk)
output *= join([repr(c) for c in chunk.content], "")
else
output *= "\n" * "```julia"
isempty(chunk.optionstring) || (output *= ";" * chunk.optionstring)
@ -128,8 +126,8 @@ end
function convert_doc(doc::WeaveDoc, format::NowebOutput)
output = ""
for chunk in doc.chunks
if typeof(chunk) == Weave.DocChunk
output *= chunk.content
if isa(chunk, DocChunk)
output *= join([repr(c) for c in chunk.content], "")
else
output *= "\n" * "<<"
isempty(chunk.optionstring) || (output *= strip(chunk.optionstring))
@ -147,7 +145,8 @@ function convert_doc(doc::WeaveDoc, format::ScriptOutput)
output = ""
for chunk in doc.chunks
if typeof(chunk) == Weave.DocChunk
output *= join(["#' " * s for s in split(chunk.content, "\n")], "\n")
content = join([repr(c) for c in chunk.content], "")
output *= join(["#' " * s for s in split(content, "\n")], "\n")
else
output *= "\n#+ "
isempty(chunk.optionstring) || (output *= strip(chunk.optionstring))
@ -158,3 +157,11 @@ function convert_doc(doc::WeaveDoc, format::ScriptOutput)
return output
end
function Base.repr(c::InlineText)
return c.content
end
function Base.repr(c::InlineCode)
return "`j $(c.content)`"
end

View File

@ -20,7 +20,19 @@ Weave.weave("documents/chunk_cache.noweb", plotlib=nothing, cache=:user);
cached_result = readstring(out)
@test result == cached_result
if VERSION.minor == 3
# cache = :all
isdir("documents/cache") && rm("documents/cache", recursive = true)
out = "documents/chunk_cache.md"
Weave.weave("documents/chunk_cache.noweb", cache=:all);
result = readstring(out)
rm(out)
Weave.weave("documents/chunk_cache.noweb", cache=:all);
cached_result = readstring(out)
@test result == cached_result
if VERSION.minor == 5
using Gadfly
isdir("documents/cache") && rm("documents/cache", recursive = true)
#Caching with Gadfly

View File

@ -3,6 +3,7 @@
y= [2, 5, 12]
@
Some text `j print(10)` and continues with another inline code `j y`.
<<term=true; cache=true>>=
y= [2, 5, 12]
@ -26,6 +27,7 @@ y = 1:5
println(y)
@
`j y`
<<cache=true>>=
y = 1:5

View File

@ -80,4 +80,4 @@ h_ref = """
and some text
"""
@test htext.content == h_ref
@test htext.content[1].content == h_ref