Browse Source

Formatted

pull/200/head
J S 3 years ago
parent
commit
fa050c7863
  1. 23
      docs/make.jl
  2. 4
      docs/src/outputformats.jl
  3. 2
      examples/README.jl
  4. 6
      examples/example.jl
  5. 7
      src/IJulia.jl
  6. 245
      src/Literate.jl
  7. 2259
      test/runtests.jl

23
docs/make.jl

@ -5,7 +5,11 @@ if haskey(ENV, "GITHUB_ACTIONS")
end end
deployconfig = Documenter.auto_detect_deploy_system() deployconfig = Documenter.auto_detect_deploy_system()
Documenter.post_status(deployconfig; type="pending", repo="github.com/fredrikekre/Literate.jl.git") Documenter.post_status(
deployconfig;
type = "pending",
repo = "github.com/fredrikekre/Literate.jl.git",
)
using Literate using Literate
using Plots # to not capture precompilation output using Plots # to not capture precompilation output
@ -14,7 +18,7 @@ EXAMPLE = joinpath(@__DIR__, "..", "examples", "example.jl")
OUTPUT = joinpath(@__DIR__, "src/generated") OUTPUT = joinpath(@__DIR__, "src/generated")
function preprocess(str) function preprocess(str)
str = replace(str, "x = 123" => "y = 321"; count=1) str = replace(str, "x = 123" => "y = 321"; count = 1)
return str return str
end end
@ -23,7 +27,12 @@ Literate.notebook(EXAMPLE, OUTPUT, preprocess = preprocess)
Literate.script(EXAMPLE, OUTPUT, preprocess = preprocess) Literate.script(EXAMPLE, OUTPUT, preprocess = preprocess)
# generate the example notebook for the documentation, keep in sync with outputformats.md # generate the example notebook for the documentation, keep in sync with outputformats.md
Literate.markdown(joinpath(@__DIR__, "src/outputformats.jl"), OUTPUT; credit = false, name = "name") Literate.markdown(
joinpath(@__DIR__, "src/outputformats.jl"),
OUTPUT;
credit = false,
name = "name",
)
Literate.notebook(joinpath(@__DIR__, "src/outputformats.jl"), OUTPUT; name = "notebook") Literate.notebook(joinpath(@__DIR__, "src/outputformats.jl"), OUTPUT; name = "notebook")
Literate.script(joinpath(@__DIR__, "src/outputformats.jl"), OUTPUT; credit = false) Literate.script(joinpath(@__DIR__, "src/outputformats.jl"), OUTPUT; credit = false)
@ -41,7 +50,10 @@ if haskey(ENV, "GITHUB_ACTIONS")
end end
url = "https://nbviewer.jupyter.org/github/fredrikekre/Literate.jl/blob/gh-pages/$(folder)/" url = "https://nbviewer.jupyter.org/github/fredrikekre/Literate.jl/blob/gh-pages/$(folder)/"
str = read(joinpath(@__DIR__, "src/outputformats.md"), String) str = read(joinpath(@__DIR__, "src/outputformats.md"), String)
str = replace(str, "[notebook.ipynb](generated/notebook.ipynb)." => "[notebook.ipynb]($(url)generated/notebook.ipynb).") str = replace(
str,
"[notebook.ipynb](generated/notebook.ipynb)." => "[notebook.ipynb]($(url)generated/notebook.ipynb).",
)
write(joinpath(@__DIR__, "src/outputformats.md"), str) write(joinpath(@__DIR__, "src/outputformats.md"), str)
end end
@ -61,7 +73,8 @@ makedocs(
"customprocessing.md", "customprocessing.md",
"documenter.md", "documenter.md",
"tips.md", "tips.md",
"generated/example.md"] "generated/example.md",
],
) )
deploydocs( deploydocs(

4
docs/src/outputformats.jl

@ -3,9 +3,9 @@
# In julia rational numbers can be constructed with the `//` operator. # In julia rational numbers can be constructed with the `//` operator.
# Lets define two rational numbers, `x` and `y`: # Lets define two rational numbers, `x` and `y`:
x = 1//3 x = 1 // 3
#- #-
y = 2//5 y = 2 // 5
# When adding `x` and `y` together we obtain a new rational number: # When adding `x` and `y` together we obtain a new rational number:

2
examples/README.jl

@ -20,7 +20,7 @@
# running these commands from the package root of Literate.jl: # running these commands from the package root of Literate.jl:
using Literate using Literate
Literate.markdown("examples/README.jl", "."; flavor=Literate.CommonMarkFlavor()) Literate.markdown("examples/README.jl", "."; flavor = Literate.CommonMarkFlavor())
# [docs-img]: https://img.shields.io/badge/docs-latest%20release-blue.svg # [docs-img]: https://img.shields.io/badge/docs-latest%20release-blue.svg
# [docs-url]: https://fredrikekre.github.io/Literate.jl/ # [docs-url]: https://fredrikekre.github.io/Literate.jl/

6
examples/example.jl

@ -33,8 +33,8 @@
# as markdown, and all the other lines are interpreted as code. Here is some code: # as markdown, and all the other lines are interpreted as code. Here is some code:
#nb %% A slide [code] {"slideshow": {"slide_type": "fragment"}} #nb %% A slide [code] {"slideshow": {"slide_type": "fragment"}}
x = 1//3 x = 1 // 3
y = 2//5 y = 2 // 5
#nb # %% A slide [markdown] {"slideshow": {"slide_type": "subslide"}} #nb # %% A slide [markdown] {"slideshow": {"slide_type": "subslide"}}
# In markdown sections we can use markdown syntax. For example, we can # In markdown sections we can use markdown syntax. For example, we can
@ -96,7 +96,7 @@ foo()
#nb %% A slide [code] {"slideshow": {"slide_type": "subslide"}} #nb %% A slide [code] {"slideshow": {"slide_type": "subslide"}}
using Plots using Plots
x = range(0, stop=6π, length=1000) x = range(0, stop = 6π, length = 1000)
y1 = sin.(x) y1 = sin.(x)
y2 = cos.(x) y2 = cos.(x)
plot(x, [y1, y2]) plot(x, [y1, y2])

7
src/IJulia.jl

@ -19,7 +19,8 @@ const application_vnd_vegalite_v2 = MIME("application/vnd.vegalite.v2+json")
function display_dict(x) function display_dict(x)
data = Dict{String,Any}("text/plain" => limitstringmime(text_plain, x)) data = Dict{String,Any}("text/plain" => limitstringmime(text_plain, x))
if showable(application_vnd_vegalite_v2, x) if showable(application_vnd_vegalite_v2, x)
data[string(application_vnd_vegalite_v2)] = JSON.parse(limitstringmime(application_vnd_vegalite_v2, x)) data[string(application_vnd_vegalite_v2)] =
JSON.parse(limitstringmime(application_vnd_vegalite_v2, x))
end end
if showable(image_svg, x) if showable(image_svg, x)
data[string(image_svg)] = limitstringmime(image_svg, x) data[string(image_svg)] = limitstringmime(image_svg, x)
@ -57,14 +58,14 @@ function limitstringmime(mime::MIME, x)
if israwtext(mime, x) if israwtext(mime, x)
return String(x) return String(x)
else else
show(IOContext(buf, :limit=>true, :color=>true), mime, x) show(IOContext(buf, :limit => true, :color => true), mime, x)
end end
else else
b64 = Base64EncodePipe(buf) b64 = Base64EncodePipe(buf)
if isa(x, Vector{UInt8}) if isa(x, Vector{UInt8})
write(b64, x) # x assumed to be raw binary data write(b64, x) # x assumed to be raw binary data
else else
show(IOContext(b64, :limit=>true, :color=>true), mime, x) show(IOContext(b64, :limit => true, :color => true), mime, x)
end end
close(b64) close(b64)
end end

245
src/Literate.jl

@ -44,7 +44,9 @@ mutable struct CodeChunk <: Chunk
end end
CodeChunk() = CodeChunk(String[], false) CodeChunk() = CodeChunk(String[], false)
ismdline(line) = (occursin(r"^\h*#$", line) || occursin(r"^\h*# .*$", line)) && !occursin(r"^\h*##", line) ismdline(line) =
(occursin(r"^\h*#$", line) || occursin(r"^\h*# .*$", line)) &&
!occursin(r"^\h*##", line)
function parse(content; allow_continued = true) function parse(content; allow_continued = true)
lines = collect(eachline(IOBuffer(content))) lines = collect(eachline(IOBuffer(content)))
@ -78,7 +80,10 @@ function parse(content; allow_continued = true)
end end
# remove "## " and "##\n", strip the leading "#" from "## xyz" and "##| xyz" # remove "## " and "##\n", strip the leading "#" from "## xyz" and "##| xyz"
# Note: accepts only standard space character (not no-break space U+00A0) # Note: accepts only standard space character (not no-break space U+00A0)
line = replace(replace(line, r"^(\h*)#(#(:? |\|).*)$" => s"\1\2"), r"^(\h*#)#$" => s"\1") line = replace(
replace(line, r"^(\h*)#(#(:? |\|).*)$" => s"\1\2"),
r"^(\h*#)#$" => s"\1",
)
push!(chunks[end].lines, line) push!(chunks[end].lines, line)
end end
end end
@ -108,7 +113,10 @@ function parse(content; allow_continued = true)
append!(merged_chunks[end].lines, chunk.lines) append!(merged_chunks[end].lines, chunk.lines)
else # need to put back "#" else # need to put back "#"
for line in chunk.lines for line in chunk.lines
push!(merged_chunks[end].lines, rstrip(line.first * "# " * line.second)) push!(
merged_chunks[end].lines,
rstrip(line.first * "# " * line.second),
)
end end
end end
else else
@ -124,11 +132,7 @@ function parse(content; allow_continued = true)
return chunks return chunks
end end
function replace_default(content, sym; function replace_default(content, sym; config::Dict, branch = "gh-pages", commit = "master")
config::Dict,
branch = "gh-pages",
commit = "master"
)
repls = Pair{Any,Any}[] repls = Pair{Any,Any}[]
# add some shameless advertisement # add some shameless advertisement
@ -159,7 +163,7 @@ function replace_default(content, sym;
newlines = sprint() do io newlines = sprint() do io
foreach(l -> println(io, "# ", l), eachline(IOBuffer(m[1]))) foreach(l -> println(io, "# ", l), eachline(IOBuffer(m[1])))
end end
str = replace(str, multiline_r => chop(newlines); count=1) str = replace(str, multiline_r => chop(newlines); count = 1)
end end
return str return str
end end
@ -199,12 +203,16 @@ function replace_default(content, sym;
# fix links # fix links
if get(ENV, "DOCUMENTATIONGENERATOR", "") == "true" if get(ENV, "DOCUMENTATIONGENERATOR", "") == "true"
## DocumentationGenerator.jl ## DocumentationGenerator.jl
base_url = get(ENV, "DOCUMENTATIONGENERATOR_BASE_URL", "DOCUMENTATIONGENERATOR_BASE_URL") base_url =
get(ENV, "DOCUMENTATIONGENERATOR_BASE_URL", "DOCUMENTATIONGENERATOR_BASE_URL")
nbviewer_root_url = "https://nbviewer.jupyter.org/urls/$(base_url)" nbviewer_root_url = "https://nbviewer.jupyter.org/urls/$(base_url)"
push!(repls, "@__NBVIEWER_ROOT_URL__" => nbviewer_root_url) push!(repls, "@__NBVIEWER_ROOT_URL__" => nbviewer_root_url)
else else
push!(repls, "@__REPO_ROOT_URL__" => get(config, "repo_root_url", "<unknown>")) push!(repls, "@__REPO_ROOT_URL__" => get(config, "repo_root_url", "<unknown>"))
push!(repls, "@__NBVIEWER_ROOT_URL__" => get(config, "nbviewer_root_url", "<unknown>")) push!(
repls,
"@__NBVIEWER_ROOT_URL__" => get(config, "nbviewer_root_url", "<unknown>"),
)
push!(repls, "@__BINDER_ROOT_URL__" => get(config, "binder_root_url", "<unknown>")) push!(repls, "@__BINDER_ROOT_URL__" => get(config, "binder_root_url", "<unknown>"))
end end
@ -227,8 +235,11 @@ end
filename(str) = first(splitext(last(splitdir(str)))) filename(str) = first(splitext(last(splitdir(str))))
isdocumenter(cfg) = cfg["flavor"]::AbstractFlavor isa DocumenterFlavor isdocumenter(cfg) = cfg["flavor"]::AbstractFlavor isa DocumenterFlavor
_DEFAULT_IMAGE_FORMATS = [(MIME("image/svg+xml"), ".svg"), (MIME("image/png"), ".png"), _DEFAULT_IMAGE_FORMATS = [
(MIME("image/jpeg"), ".jpeg")] (MIME("image/svg+xml"), ".svg"),
(MIME("image/png"), ".png"),
(MIME("image/jpeg"), ".jpeg"),
]
# Cache of inputfile => head branch # Cache of inputfile => head branch
const HEAD_BRANCH_CACHE = Dict{String,String}() const HEAD_BRANCH_CACHE = Dict{String,String}()
@ -245,9 +256,9 @@ function edit_commit(inputfile, user_config)
git_root = try git_root = try
readchomp( readchomp(
pipeline( pipeline(
setenv(`$(git) rev-parse --show-toplevel`; dir=dirname(inputfile)); setenv(`$(git) rev-parse --show-toplevel`; dir = dirname(inputfile));
stderr=devnull, stderr = devnull,
) ),
) )
catch catch
end end
@ -266,8 +277,8 @@ function edit_commit(inputfile, user_config)
str = try str = try
read( read(
pipeline( pipeline(
setenv(`$(git) remote show origin`, env; dir=dirname(inputfile)), setenv(`$(git) remote show origin`, env; dir = dirname(inputfile)),
stderr=devnull, stderr = devnull,
), ),
String, String,
) )
@ -283,16 +294,20 @@ function edit_commit(inputfile, user_config)
end end
# Default to DefaultFlavor() setting # Default to DefaultFlavor() setting
pick_codefence(flavor::AbstractFlavor,execute::Bool,name::AbstractString)=pick_codefence(DefaultFlavor(),execute,name) pick_codefence(flavor::AbstractFlavor, execute::Bool, name::AbstractString) =
pick_codefence(flavor::DefaultFlavor,execute::Bool,name::AbstractString)=("````julia" => "````") pick_codefence(DefaultFlavor(), execute, name)
pick_codefence(flavor::DocumenterFlavor,execute::Bool,name::AbstractString)=(execute ? pick_codefence(flavor::DefaultFlavor, execute::Bool, name::AbstractString) =
pick_codefence(DefaultFlavor(),execute,name) : ("````@example $(name)" => "````") ("````julia" => "````")
pick_codefence(flavor::DocumenterFlavor, execute::Bool, name::AbstractString) = (
execute ? pick_codefence(DefaultFlavor(), execute, name) :
("````@example $(name)" => "````")
) )
pick_codefence(flavor::QuartoFlavor,execute::Bool,name::AbstractString)=(execute ? pick_codefence(flavor::QuartoFlavor, execute::Bool, name::AbstractString) = (
error("QuartoFlavor does not support argument execute=true!") : ("```{julia}" => "```") execute ? error("QuartoFlavor does not support argument execute=true!") :
("```{julia}" => "```")
) )
function create_configuration(inputfile; user_config, user_kwargs, type=nothing) function create_configuration(inputfile; user_config, user_kwargs, type = nothing)
# Combine user config with user kwargs # Combine user config with user kwargs
user_config = Dict{String,Any}(string(k) => v for (k, v) in user_config) user_config = Dict{String,Any}(string(k) => v for (k, v) in user_config)
user_kwargs = Dict{String,Any}(string(k) => v for (k, v) in user_kwargs) user_kwargs = Dict{String,Any}(string(k) => v for (k, v) in user_kwargs)
@ -301,16 +316,25 @@ function create_configuration(inputfile; user_config, user_kwargs, type=nothing)
# deprecation of documenter kwarg # deprecation of documenter kwarg
if (d = get(user_config, "documenter", nothing); d !== nothing) if (d = get(user_config, "documenter", nothing); d !== nothing)
if type === :md if type === :md
Base.depwarn("The documenter=$(d) keyword to Literate.markdown is deprecated." * Base.depwarn(
"The documenter=$(d) keyword to Literate.markdown is deprecated." *
" Pass `flavor = Literate.$(d ? "DocumenterFlavor" : "CommonMarkFlavor")()`" * " Pass `flavor = Literate.$(d ? "DocumenterFlavor" : "CommonMarkFlavor")()`" *
" instead.", Symbol("Literate.markdown")) " instead.",
Symbol("Literate.markdown"),
)
user_config["flavor"] = d ? DocumenterFlavor() : CommonMarkFlavor() user_config["flavor"] = d ? DocumenterFlavor() : CommonMarkFlavor()
elseif type === :nb elseif type === :nb
Base.depwarn("The documenter=$(d) keyword to Literate.notebook is deprecated." * Base.depwarn(
" It is not used anymore for notebook output.", Symbol("Literate.notebook")) "The documenter=$(d) keyword to Literate.notebook is deprecated." *
" It is not used anymore for notebook output.",
Symbol("Literate.notebook"),
)
elseif type === :jl elseif type === :jl
Base.depwarn("The documenter=$(d) keyword to Literate.script is deprecated." * Base.depwarn(
" It is not used anymore for script output.", Symbol("Literate.script")) "The documenter=$(d) keyword to Literate.script is deprecated." *
" It is not used anymore for script output.",
Symbol("Literate.script"),
)
end end
end end
@ -327,7 +351,7 @@ function create_configuration(inputfile; user_config, user_kwargs, type=nothing)
cfg["codefence"] = pick_codefence( cfg["codefence"] = pick_codefence(
get(user_config, "flavor", cfg["flavor"]), get(user_config, "flavor", cfg["flavor"]),
get(user_config, "execute", cfg["execute"]), get(user_config, "execute", cfg["execute"]),
get(user_config, "name", replace(cfg["name"], r"\s" => "_")) get(user_config, "name", replace(cfg["name"], r"\s" => "_")),
) )
cfg["image_formats"] = _DEFAULT_IMAGE_FORMATS cfg["image_formats"] = _DEFAULT_IMAGE_FORMATS
cfg["edit_commit"] = edit_commit(inputfile, user_config) cfg["edit_commit"] = edit_commit(inputfile, user_config)
@ -367,7 +391,8 @@ function create_configuration(inputfile; user_config, user_kwargs, type=nothing)
else else
get(user_config, "devurl", "dev") get(user_config, "devurl", "dev")
end end
elseif (m = match(r"refs\/pull\/(\d+)\/merge", get(ENV, "GITHUB_REF", ""))) !== nothing elseif (m = match(r"refs\/pull\/(\d+)\/merge", get(ENV, "GITHUB_REF", ""))) !==
nothing
"previews/PR$(m.captures[1])" "previews/PR$(m.captures[1])"
else else
"dev" "dev"
@ -443,12 +468,16 @@ Available options:
`$(_DEFAULT_IMAGE_FORMATS)`. Results which are `showable` with a MIME type are saved with `$(_DEFAULT_IMAGE_FORMATS)`. Results which are `showable` with a MIME type are saved with
the first match, with the corresponding extension. the first match, with the corresponding extension.
""" """
const DEFAULT_CONFIGURATION=nothing # Dummy const for documentation const DEFAULT_CONFIGURATION = nothing # Dummy const for documentation
function preprocessor(inputfile, outputdir; user_config, user_kwargs, type) function preprocessor(inputfile, outputdir; user_config, user_kwargs, type)
# Create configuration by merging default and userdefined # Create configuration by merging default and userdefined
config = create_configuration(inputfile; user_config=user_config, config = create_configuration(
user_kwargs=user_kwargs, type=type) inputfile;
user_config = user_config,
user_kwargs = user_kwargs,
type = type,
)
# normalize paths # normalize paths
inputfile = normpath(inputfile) inputfile = normpath(inputfile)
@ -457,15 +486,21 @@ function preprocessor(inputfile, outputdir; user_config, user_kwargs, type)
mkpath(outputdir) mkpath(outputdir)
outputdir = realpath(abspath(outputdir)) outputdir = realpath(abspath(outputdir))
isdir(outputdir) || error("not a directory: $(outputdir)") isdir(outputdir) || error("not a directory: $(outputdir)")
ext = type === (:nb) ? ".ipynb" : (type === (:md) && config["flavor"] isa QuartoFlavor) ? ".qmd" : ".$(type)" ext =
type === (:nb) ? ".ipynb" :
(type === (:md) && config["flavor"] isa QuartoFlavor) ? ".qmd" : ".$(type)"
outputfile = joinpath(outputdir, config["name"]::String * ext) outputfile = joinpath(outputdir, config["name"]::String * ext)
if inputfile == outputfile if inputfile == outputfile
throw(ArgumentError("outputfile (`$outputfile`) is identical to inputfile (`$inputfile`)")) throw(
ArgumentError(
"outputfile (`$outputfile`) is identical to inputfile (`$inputfile`)",
),
)
end end
output_thing = type === (:md) ? "markdown page" : output_thing =
type === (:nb) ? "notebook" : type === (:md) ? "markdown page" :
type === (:jl) ? "plain script file" : error("nope") type === (:nb) ? "notebook" : type === (:jl) ? "plain script file" : error("nope")
@info "generating $(output_thing) from `$(Base.contractuser(inputfile))`" @info "generating $(output_thing) from `$(Base.contractuser(inputfile))`"
# Add some information for passing around Literate methods # Add some information for passing around Literate methods
@ -494,7 +529,7 @@ function preprocessor(inputfile, outputdir; user_config, user_kwargs, type)
end end
# default replacements # default replacements
content = replace_default(content, type; config=config) content = replace_default(content, type; config = config)
# parse the content into chunks # parse the content into chunks
chunks = parse(content; allow_continued = type !== :nb) chunks = parse(content; allow_continued = type !== :nb)
@ -502,7 +537,7 @@ function preprocessor(inputfile, outputdir; user_config, user_kwargs, type)
return chunks, config return chunks, config
end end
function write_result(content, config; print=print) function write_result(content, config; print = print)
outputfile = config["literate_outputfile"] outputfile = config["literate_outputfile"]
@info "writing result to `$(Base.contractuser(outputfile))`" @info "writing result to `$(Base.contractuser(outputfile))`"
open(outputfile, "w") do io open(outputfile, "w") do io
@ -519,10 +554,15 @@ Generate a plain script file from `inputfile` and write the result to `outputdir
See the manual section on [Configuration](@ref) for documentation See the manual section on [Configuration](@ref) for documentation
of possible configuration with `config` and other keyword arguments. of possible configuration with `config` and other keyword arguments.
""" """
function script(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwargs...) function script(inputfile, outputdir = pwd(); config::AbstractDict = Dict(), kwargs...)
# preprocessing and parsing # preprocessing and parsing
chunks, config = chunks, config = preprocessor(
preprocessor(inputfile, outputdir; user_config=config, user_kwargs=kwargs, type=:jl) inputfile,
outputdir;
user_config = config,
user_kwargs = kwargs,
type = :jl,
)
# create the script file # create the script file
ioscript = IOBuffer() ioscript = IOBuffer()
@ -558,10 +598,15 @@ to the directory `outputdir`.
See the manual section on [Configuration](@ref) for documentation See the manual section on [Configuration](@ref) for documentation
of possible configuration with `config` and other keyword arguments. of possible configuration with `config` and other keyword arguments.
""" """
function markdown(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwargs...) function markdown(inputfile, outputdir = pwd(); config::AbstractDict = Dict(), kwargs...)
# preprocessing and parsing # preprocessing and parsing
chunks, config = chunks, config = preprocessor(
preprocessor(inputfile, outputdir; user_config=config, user_kwargs=kwargs, type=:md) inputfile,
outputdir;
user_config = config,
user_kwargs = kwargs,
type = :md,
)
# create the markdown file # create the markdown file
sb = sandbox() sb = sandbox()
@ -578,7 +623,9 @@ function markdown(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwarg
write(iocode, codefence.first) write(iocode, codefence.first)
# make sure the code block is finalized if we are printing to ```@example # make sure the code block is finalized if we are printing to ```@example
# (or ````@example, any number of backticks >= 3 works) # (or ````@example, any number of backticks >= 3 works)
if chunk.continued && occursin(r"^`{3,}@example", codefence.first) && isdocumenter(config) if chunk.continued &&
occursin(r"^`{3,}@example", codefence.first) &&
isdocumenter(config)
write(iocode, "; continued = true") write(iocode, "; continued = true")
end end
write(iocode, '\n') write(iocode, '\n')
@ -596,11 +643,16 @@ function markdown(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwarg
any(write_line, chunk.lines) && write(iomd, seekstart(iocode)) any(write_line, chunk.lines) && write(iomd, seekstart(iocode))
if execute if execute
cd(config["literate_outputdir"]) do cd(config["literate_outputdir"]) do
execute_markdown!(iomd, sb, join(chunk.lines, '\n'), outputdir; execute_markdown!(
inputfile=config["literate_inputfile"], iomd,
fake_source=config["literate_outputfile"], sb,
flavor=config["flavor"], join(chunk.lines, '\n'),
image_formats=config["image_formats"]) outputdir;
inputfile = config["literate_inputfile"],
fake_source = config["literate_outputfile"],
flavor = config["flavor"],
image_formats = config["image_formats"],
)
end end
end end
end end
@ -615,18 +667,26 @@ function markdown(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwarg
return outputfile return outputfile
end end
function execute_markdown!(io::IO, sb::Module, block::String, outputdir; function execute_markdown!(
inputfile::String, fake_source::String, io::IO,
flavor::AbstractFlavor, image_formats::Vector) sb::Module,
block::String,
outputdir;
inputfile::String,
fake_source::String,
flavor::AbstractFlavor,
image_formats::Vector,
)
# TODO: Deal with explicit display(...) calls # TODO: Deal with explicit display(...) calls
r, str, _ = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source) r, str, _ = execute_block(sb, block; inputfile = inputfile, fake_source = fake_source)
# issue #101: consecutive codefenced blocks need newline # issue #101: consecutive codefenced blocks need newline
# issue #144: quadruple backticks allow for triple backticks in the output # issue #144: quadruple backticks allow for triple backticks in the output
plain_fence = "\n````\n" => "\n````" plain_fence = "\n````\n" => "\n````"
if r !== nothing && !REPL.ends_with_semicolon(block) if r !== nothing && !REPL.ends_with_semicolon(block)
if (flavor isa FranklinFlavor || flavor isa DocumenterFlavor) && if (flavor isa FranklinFlavor || flavor isa DocumenterFlavor) &&
Base.invokelatest(showable, MIME("text/html"), r) Base.invokelatest(showable, MIME("text/html"), r)
htmlfence = flavor isa FranklinFlavor ? ("~~~" => "~~~") : ("```@raw html" => "```") htmlfence =
flavor isa FranklinFlavor ? ("~~~" => "~~~") : ("```@raw html" => "```")
write(io, "\n", htmlfence.first, "\n") write(io, "\n", htmlfence.first, "\n")
Base.invokelatest(show, io, MIME("text/html"), r) Base.invokelatest(show, io, MIME("text/html"), r)
write(io, "\n", htmlfence.second, "\n") write(io, "\n", htmlfence.second, "\n")
@ -668,8 +728,8 @@ function parse_nbmeta(line)
# Cf. https://jupytext.readthedocs.io/en/latest/formats.html#the-percent-format # Cf. https://jupytext.readthedocs.io/en/latest/formats.html#the-percent-format
m = match(r"^%% ([^[{]+)?\s*(?:\[(\w+)\])?\s*(\{.*)?$", line) m = match(r"^%% ([^[{]+)?\s*(?:\[(\w+)\])?\s*(\{.*)?$", line)
typ = m.captures[2] typ = m.captures[2]
name = m.captures[1] === nothing ? Dict{String, String}() : Dict("name" => m.captures[1]) name = m.captures[1] === nothing ? Dict{String,String}() : Dict("name" => m.captures[1])
meta = m.captures[3] === nothing ? Dict{String, Any}() : JSON.parse(m.captures[3]) meta = m.captures[3] === nothing ? Dict{String,Any}() : JSON.parse(m.captures[3])
return typ, merge(name, meta) return typ, merge(name, meta)
end end
line_is_nbmeta(line::Pair) = line_is_nbmeta(line.second) line_is_nbmeta(line::Pair) = line_is_nbmeta(line.second)
@ -683,16 +743,21 @@ Generate a notebook from `inputfile` and write the result to `outputdir`.
See the manual section on [Configuration](@ref) for documentation See the manual section on [Configuration](@ref) for documentation
of possible configuration with `config` and other keyword arguments. of possible configuration with `config` and other keyword arguments.
""" """
function notebook(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwargs...) function notebook(inputfile, outputdir = pwd(); config::AbstractDict = Dict(), kwargs...)
# preprocessing and parsing # preprocessing and parsing
chunks, config = chunks, config = preprocessor(
preprocessor(inputfile, outputdir; user_config=config, user_kwargs=kwargs, type=:nb) inputfile,
outputdir;
user_config = config,
user_kwargs = kwargs,
type = :nb,
)
# create the notebook # create the notebook
nb = jupyter_notebook(chunks, config) nb = jupyter_notebook(chunks, config)
# write to file # write to file
outputfile = write_result(nb, config; print = (io, c)->JSON.print(io, c, 1)) outputfile = write_result(nb, config; print = (io, c) -> JSON.print(io, c, 1))
return outputfile return outputfile
end end
@ -708,14 +773,16 @@ function jupyter_notebook(chunks, config)
chunktype = isa(chunk, MDChunk) ? "markdown" : "code" chunktype = isa(chunk, MDChunk) ? "markdown" : "code"
if !isempty(chunk.lines) && line_is_nbmeta(chunk.lines[1]) if !isempty(chunk.lines) && line_is_nbmeta(chunk.lines[1])
metatype, metadata = parse_nbmeta(chunk.lines[1]) metatype, metadata = parse_nbmeta(chunk.lines[1])
metatype !== nothing && metatype != chunktype && error("specifying a different cell type is not supported") metatype !== nothing &&
metatype != chunktype &&
error("specifying a different cell type is not supported")
popfirst!(chunk.lines) popfirst!(chunk.lines)
else else
metadata = Dict{String, Any}() metadata = Dict{String,Any}()
end end
lines = isa(chunk, MDChunk) ? lines =
String[x.second for x in chunk.lines] : # skip indent isa(chunk, MDChunk) ? String[x.second for x in chunk.lines] : # skip indent
chunk.lines chunk.lines
@views map!(x -> x * '\n', lines[1:end-1], lines[1:end-1]) @views map!(x -> x * '\n', lines[1:end-1], lines[1:end-1])
cell["cell_type"] = chunktype cell["cell_type"] = chunktype
cell["metadata"] = metadata cell["metadata"] = metadata
@ -732,15 +799,15 @@ function jupyter_notebook(chunks, config)
metadata = Dict() metadata = Dict()
kernelspec = Dict() kernelspec = Dict()
kernelspec["language"] = "julia" kernelspec["language"] = "julia"
kernelspec["name"] = "julia-$(VERSION.major).$(VERSION.minor)" kernelspec["name"] = "julia-$(VERSION.major).$(VERSION.minor)"
kernelspec["display_name"] = "Julia $(string(VERSION))" kernelspec["display_name"] = "Julia $(string(VERSION))"
metadata["kernelspec"] = kernelspec metadata["kernelspec"] = kernelspec
language_info = Dict() language_info = Dict()
language_info["file_extension"] = ".jl" language_info["file_extension"] = ".jl"
language_info["mimetype"] = "application/julia" language_info["mimetype"] = "application/julia"
language_info["name"]= "julia" language_info["name"] = "julia"
language_info["version"] = string(VERSION) language_info["version"] = string(VERSION)
metadata["language_info"] = language_info metadata["language_info"] = language_info
@ -753,8 +820,11 @@ function jupyter_notebook(chunks, config)
@info "executing notebook `$(config["name"] * ".ipynb")`" @info "executing notebook `$(config["name"] * ".ipynb")`"
try try
cd(config["literate_outputdir"]) do cd(config["literate_outputdir"]) do
nb = execute_notebook(nb; inputfile=config["literate_inputfile"], nb = execute_notebook(
fake_source=config["literate_outputfile"]) nb;
inputfile = config["literate_inputfile"],
fake_source = config["literate_outputfile"],
)
end end
catch err catch err
@error "error when executing notebook based on input file: " * @error "error when executing notebook based on input file: " *
@ -773,7 +843,8 @@ function execute_notebook(nb; inputfile::String, fake_source::String)
execution_count += 1 execution_count += 1
cell["execution_count"] = execution_count cell["execution_count"] = execution_count
block = join(cell["source"]) block = join(cell["source"])
r, str, display_dicts = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source) r, str, display_dicts =
execute_block(sb, block; inputfile = inputfile, fake_source = fake_source)
# str should go into stream # str should go into stream
if !isempty(str) if !isempty(str)
@ -875,14 +946,16 @@ function execute_block(sb::Module, block::String; inputfile::String, fake_source
end end
popdisplay(disp) # IOCapture.capture has a try-catch so should always end up here popdisplay(disp) # IOCapture.capture has a try-catch so should always end up here
if c.error if c.error
error(""" error(
$(sprint(showerror, c.value)) """
when executing the following code block from inputfile `$(Base.contractuser(inputfile))` $(sprint(showerror, c.value))
when executing the following code block from inputfile `$(Base.contractuser(inputfile))`
```julia
$block ```julia
``` $block
""") ```
""",
)
end end
return c.value, c.output, disp.data return c.value, c.output, disp.data
end end

2259
test/runtests.jl

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save