|
|
|
@ -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 |
|
|
|
|