diff --git a/src/Literate.jl b/src/Literate.jl index c33c9a0..8284d09 100644 --- a/src/Literate.jl +++ b/src/Literate.jl @@ -16,7 +16,7 @@ struct DefaultFlavor <: AbstractFlavor end struct DocumenterFlavor <: AbstractFlavor end struct CommonMarkFlavor <: AbstractFlavor end struct FranklinFlavor <: AbstractFlavor end -struct CarpentriesFlavor <: AbstractFlavor end +struct CarpentriesFlavor <: AbstractFlavor end # # Some simple rules: # @@ -46,7 +46,7 @@ CodeChunk() = CodeChunk(String[], false) 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))) chunks = Chunk[] @@ -124,10 +124,10 @@ function parse(content; allow_continued = true) end function replace_default(content, sym; - config::Dict, - branch = "gh-pages", - commit = "master" - ) + config::Dict, + branch="gh-pages", + commit="master" +) repls = Pair{Any,Any}[] # add some shameless advertisement @@ -227,7 +227,7 @@ filename(str) = first(splitext(last(splitdir(str)))) isdocumenter(cfg) = cfg["flavor"]::AbstractFlavor isa DocumenterFlavor _DEFAULT_IMAGE_FORMATS = [(MIME("image/svg+xml"), ".svg"), (MIME("image/png"), ".png"), - (MIME("image/jpeg"), ".jpeg")] + (MIME("image/jpeg"), ".jpeg")] # Cache of inputfile => head branch const HEAD_BRANCH_CACHE = Dict{String,String}() @@ -245,7 +245,7 @@ function edit_commit(inputfile, user_config) readchomp( pipeline( setenv(`$(git) rev-parse --show-toplevel`; dir=dirname(inputfile)); - stderr=devnull, + stderr=devnull ) ) catch @@ -291,15 +291,15 @@ function create_configuration(inputfile; user_config, user_kwargs, type=nothing) if (d = get(user_config, "documenter", nothing); d !== nothing) if type === :md Base.depwarn("The documenter=$(d) keyword to Literate.markdown is deprecated." * - " Pass `flavor = Literate.$(d ? "DocumenterFlavor" : "CommonMarkFlavor")()`" * - " instead.", Symbol("Literate.markdown")) + " Pass `flavor = Literate.$(d ? "DocumenterFlavor" : "CommonMarkFlavor")()`" * + " instead.", Symbol("Literate.markdown")) user_config["flavor"] = d ? DocumenterFlavor() : CommonMarkFlavor() elseif type === :nb Base.depwarn("The documenter=$(d) keyword to Literate.notebook is deprecated." * - " It is not used anymore for notebook output.", Symbol("Literate.notebook")) + " It is not used anymore for notebook output.", Symbol("Literate.notebook")) elseif type === :jl Base.depwarn("The documenter=$(d) keyword to Literate.script is deprecated." * - " It is not used anymore for script output.", Symbol("Literate.script")) + " It is not used anymore for script output.", Symbol("Literate.script")) end end @@ -431,7 +431,7 @@ Available options: `$(_DEFAULT_IMAGE_FORMATS)`. Results which are `showable` with a MIME type are saved with 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) # Create configuration by merging default and userdefined @@ -485,7 +485,7 @@ function preprocessor(inputfile, outputdir; user_config, user_kwargs, type) content = replace_default(content, type; config=config) # parse the content into chunks - chunks = parse(content; allow_continued = type !== :nb) + chunks = parse(content; allow_continued=type !== :nb) return chunks, config end @@ -572,7 +572,7 @@ function processNonAdmonitions(item, io) write(io, string(Markdown.MD(item))) end -function writeContent(mdContent, io) +function writeContent(mdContent, io) for item in mdContent if isa(item, Markdown.Admonition) CarpentriesAdmonition(item, io) @@ -592,7 +592,7 @@ function CarpentriesCallout(admonition, io) write(io, ":::::::: callout", '\n') else if line != "" - write(io, line, '\n') + write(io, line, '\n') end end end @@ -648,13 +648,13 @@ function CarpentriesAdmonition(admonition, io) category = admonition.category admonition = split(string(Markdown.MD(admonition)), '\n') if category in ("sc", "mc", "freecode") - CarpentriesChallenge(admonition, io) + CarpentriesChallenge(admonition, io) elseif category == "tip" - CarpentriesTestamonial(admonition, io) + CarpentriesTestamonial(admonition, io) elseif category == "warning" - CarpentriesWarning(admonition, io) + CarpentriesWarning(admonition, io) elseif category in ("info", "note") - CarpentriesCallout(admonition, io) + CarpentriesCallout(admonition, io) elseif category == "carp" CarpentriesYAML(admonition, io) end @@ -676,135 +676,91 @@ function markdown(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwarg # preprocessing and parsing chunks, config = preprocessor(inputfile, outputdir; user_config=config, user_kwargs=kwargs, type=:md) - flavor=config["flavor"] # create the markdown file - sb = sandbox() iomd = IOBuffer() - - - if flavor isa CarpentriesFlavor - for chunk in chunks - if isa(chunk, MDChunk) - if containsAdmonition(chunk) - str = chunkToMD(chunk) - mdContent = str.content - writeContent(mdContent, iomd) - else - # Handle chunks without admonitions - for line in chunk.lines - write(iomd, line.second, '\n') # Skip indent - end - end - ## `else` is copied from vanilla: - else # isa(chunk, CodeChunk) - iocode = IOBuffer() - codefence = config["codefence"]::Pair - write(iocode, codefence.first) - # make sure the code block is finalized if we are printing to ```@example - # (or ````@example, any number of backticks >= 3 works) - if chunk.continued && occursin(r"^`{3,}@example", codefence.first) && isdocumenter(config) - write(iocode, "; continued = true") - end - write(iocode, '\n') - # filter out trailing #hide unless code is executed by Documenter - execute = config["execute"]::Bool - write_hide = isdocumenter(config) && !execute - write_line(line) = write_hide || !endswith(line, "#hide") - for line in chunk.lines - write_line(line) && write(iocode, line, '\n') - end - if write_hide && REPL.ends_with_semicolon(chunk.lines[end]) - write(iocode, "nothing #hide\n") - end - write(iocode, codefence.second, '\n') - any(write_line, chunk.lines) && write(iomd, seekstart(iocode)) - if execute - cd(config["literate_outputdir"]) do - execute_markdown!(iomd, sb, join(chunk.lines, '\n'), outputdir; - inputfile=config["literate_inputfile"], - fake_source=config["literate_outputfile"], - flavor=config["flavor"], - image_formats=config["image_formats"], - file_prefix="$(config["name"])-$(chunknum)", - ) - end - end - end - end - else - # kinda Vanilla Function - for (chunknum, chunk) in enumerate(chunks) - if isa(chunk, MDChunk) + write_md_chunks!(iomd, chunks, outputdir, config) - #______________________________________________________________________________________________________________ + + # custom post-processing from user + content = config["postprocess"](String(take!(iomd))) + + # write to file + outputfile = write_result(content, config) + return outputfile + +end + +function write_md_chunks!(iomd, chunks, outputdir, config) + flavor = config["flavor"] + sb = sandbox() + for (chunknum, chunk) in enumerate(chunks) + if isa(chunk, MDChunk) + + #______________________________________________________________________________________________________________ + if flavor isa CarpentriesFlavor + if containsAdmonition(chunk) + writeContent(chunk.lines, iomd) + end + else if containsYAML(chunk) # This part is the only change. It (should) delete the YAML Admo for non Carpentries MD. continue end - #______________________________________________________________________________________________________________ + end + #______________________________________________________________________________________________________________ - for line in chunk.lines - write(iomd, line.second, '\n') # skip indent here - end - else # isa(chunk, CodeChunk) - iocode = IOBuffer() - codefence = config["codefence"]::Pair - write(iocode, codefence.first) - # make sure the code block is finalized if we are printing to ```@example - # (or ````@example, any number of backticks >= 3 works) - if chunk.continued && occursin(r"^`{3,}@example", codefence.first) && isdocumenter(config) - write(iocode, "; continued = true") - end - write(iocode, '\n') - # filter out trailing #hide unless code is executed by Documenter - execute = config["execute"]::Bool - write_hide = isdocumenter(config) && !execute - write_line(line) = write_hide || !endswith(line, "#hide") - for line in chunk.lines - write_line(line) && write(iocode, line, '\n') - end - if write_hide && REPL.ends_with_semicolon(chunk.lines[end]) - write(iocode, "nothing #hide\n") - end - write(iocode, codefence.second, '\n') - any(write_line, chunk.lines) && write(iomd, seekstart(iocode)) - if execute - cd(config["literate_outputdir"]) do + for line in chunk.lines + write(iomd, line.second, '\n') # skip indent here + end + else # isa(chunk, CodeChunk) + iocode = IOBuffer() + codefence = config["codefence"]::Pair + write(iocode, codefence.first) + # make sure the code block is finalized if we are printing to ```@example + # (or ````@example, any number of backticks >= 3 works) + if chunk.continued && occursin(r"^`{3,}@example", codefence.first) && isdocumenter(config) + write(iocode, "; continued = true") + end + write(iocode, '\n') + # filter out trailing #hide unless code is executed by Documenter + execute = config["execute"]::Bool + write_hide = isdocumenter(config) && !execute + write_line(line) = write_hide || !endswith(line, "#hide") + for line in chunk.lines + write_line(line) && write(iocode, line, '\n') + end + if write_hide && REPL.ends_with_semicolon(chunk.lines[end]) + write(iocode, "nothing #hide\n") + end + write(iocode, codefence.second, '\n') + any(write_line, chunk.lines) && write(iomd, seekstart(iocode)) + if execute + cd(config["literate_outputdir"]) do execute_markdown!(iomd, sb, join(chunk.lines, '\n'), outputdir; - inputfile=config["literate_inputfile"], - fake_source=config["literate_outputfile"], - flavor=config["flavor"], - image_formats=config["image_formats"], - file_prefix="$(config["name"])-$(chunknum)", + inputfile=config["literate_inputfile"], + fake_source=config["literate_outputfile"], + flavor=config["flavor"], + image_formats=config["image_formats"], + file_prefix="$(config["name"])-$(chunknum)" ) - end end end end + write(iomd, '\n') # add a newline between each chunk end - write(iomd, '\n') # add a newline between each chunk - - - # custom post-processing from user - content = config["postprocess"](String(take!(iomd))) - - # write to file - outputfile = write_result(content, config) - return outputfile - -end +end function execute_markdown!(io::IO, sb::Module, block::String, outputdir; - inputfile::String, fake_source::String, - flavor::AbstractFlavor, image_formats::Vector, file_prefix::String) + inputfile::String, fake_source::String, + flavor::AbstractFlavor, image_formats::Vector, file_prefix::String) # TODO: Deal with explicit display(...) calls r, str, _ = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source) # issue #101: consecutive codefenced blocks need newline # issue #144: quadruple backticks allow for triple backticks in the output - plain_fence = "\n````\n" => "\n````" + plain_fence = "\n````\n" => "\n````" # Here CarpentiresFlavor fork... if r !== nothing && !REPL.ends_with_semicolon(block) if (flavor isa FranklinFlavor || flavor isa DocumenterFlavor) && @@ -851,8 +807,8 @@ function parse_nbmeta(line) # Cf. https://jupytext.readthedocs.io/en/latest/formats.html#the-percent-format m = match(r"^%% ([^[{]+)?\s*(?:\[(\w+)\])?\s*(\{.*)?$", line) typ = m.captures[2] - 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]) + 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]) return typ, merge(name, meta) end line_is_nbmeta(line::Pair) = line_is_nbmeta(line.second) @@ -875,7 +831,7 @@ function notebook(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwarg nb = jupyter_notebook(chunks, config) # 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 end @@ -894,11 +850,11 @@ function jupyter_notebook(chunks, config) metatype !== nothing && metatype != chunktype && error("specifying a different cell type is not supported") popfirst!(chunk.lines) else - metadata = Dict{String, Any}() + metadata = Dict{String,Any}() end lines = isa(chunk, MDChunk) ? - String[x.second for x in chunk.lines] : # skip indent - chunk.lines + String[x.second for x in chunk.lines] : # skip indent + chunk.lines @views map!(x -> x * '\n', lines[1:end-1], lines[1:end-1]) cell["cell_type"] = chunktype cell["metadata"] = metadata @@ -915,15 +871,15 @@ function jupyter_notebook(chunks, config) metadata = Dict() kernelspec = Dict() - kernelspec["language"] = "julia" - kernelspec["name"] = "julia-$(VERSION.major).$(VERSION.minor)" + kernelspec["language"] = "julia" + kernelspec["name"] = "julia-$(VERSION.major).$(VERSION.minor)" kernelspec["display_name"] = "Julia $(string(VERSION))" metadata["kernelspec"] = kernelspec language_info = Dict() language_info["file_extension"] = ".jl" language_info["mimetype"] = "application/julia" - language_info["name"]= "julia" + language_info["name"] = "julia" language_info["version"] = string(VERSION) metadata["language_info"] = language_info @@ -937,7 +893,7 @@ function jupyter_notebook(chunks, config) try cd(config["literate_outputdir"]) do nb = execute_notebook(nb; inputfile=config["literate_inputfile"], - fake_source=config["literate_outputfile"]) + fake_source=config["literate_outputfile"]) end catch err @error "error when executing notebook based on input file: " * @@ -963,7 +919,7 @@ function execute_notebook(nb; inputfile::String, fake_source::String) stream = Dict{String,Any}() stream["output_type"] = "stream" stream["name"] = "stdout" - stream["text"] = collect(Any, eachline(IOBuffer(String(str)), keep = true)) + stream["text"] = collect(Any, eachline(IOBuffer(String(str)), keep=true)) push!(cell["outputs"], stream) end @@ -973,7 +929,7 @@ function execute_notebook(nb; inputfile::String, fake_source::String) function split_mime(dict) for mime in ("image/svg+xml", "text/html") if haskey(dict, mime) - dict[mime] = collect(Any, eachline(IOBuffer(dict[mime]), keep = true)) + dict[mime] = collect(Any, eachline(IOBuffer(dict[mime]), keep=true)) end end return dict @@ -1053,7 +1009,7 @@ function execute_block(sb::Module, block::String; inputfile::String, fake_source # - c.output: combined stdout and stderr # `rethrow = Union{}` means that we try-catch all the exceptions thrown in the do-block # and return them via the return value (they get handled below). - c = IOCapture.capture(rethrow = Union{}) do + c = IOCapture.capture(rethrow=Union{}) do include_string(sb, block, fake_source) end popdisplay(disp) # IOCapture.capture has a try-catch so should always end up here @@ -1070,4 +1026,4 @@ function execute_block(sb::Module, block::String; inputfile::String, fake_source return c.value, c.output, disp.data end -end # module \ No newline at end of file +end # module