Browse Source

Add quarto support

- parse properly Quarto arguments `#| echo:false`
- provide Quarto codefence (refactored to separate function)
- save files with extension `.qmd`
- all functionality tested
- updated documentation
pull/200/head
J S 3 years ago
parent
commit
24c2167baf
  1. 1
      docs/src/outputformats.md
  2. 27
      src/Literate.jl
  3. 37
      test/runtests.jl

1
docs/src/outputformats.md

@ -86,6 +86,7 @@ Literate can output markdown in different flavors. The flavor is specified using @@ -86,6 +86,7 @@ Literate can output markdown in different flavors. The flavor is specified using
[CommonMark](https://commonmark.org/) specification.
- `flavor = Literate.FranklinFlavor()`: this outputs markdown meant to be used as input
to [Franklin.jl](https://franklinjl.org/).
- `flavor = Literate.QuartoFlavor()` : this outputs markdown file (with file extension ".qmd") meant to be used with [Quarto CLI](https://quarto.org).
## [**4.2.** Notebook output](@id Notebook-output)

27
src/Literate.jl

@ -16,6 +16,7 @@ struct DefaultFlavor <: AbstractFlavor end @@ -16,6 +16,7 @@ struct DefaultFlavor <: AbstractFlavor end
struct DocumenterFlavor <: AbstractFlavor end
struct CommonMarkFlavor <: AbstractFlavor end
struct FranklinFlavor <: AbstractFlavor end
struct QuartoFlavor <: AbstractFlavor end
# # Some simple rules:
#
@ -75,8 +76,9 @@ function parse(content; allow_continued = true) @@ -75,8 +76,9 @@ function parse(content; allow_continued = true)
if !(chunks[end] isa CodeChunk)
push!(chunks, CodeChunk())
end
# remove "## " and "##\n"
line = replace(replace(line, r"^(\h*)#(# .*)$" => s"\1\2"), r"^(\h*#)#$" => s"\1")
# remove "## " and "##\n", strip the leading "#" from "## xyz" and "##| xyz"
# 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")
push!(chunks[end].lines, line)
end
end
@ -280,6 +282,16 @@ function edit_commit(inputfile, user_config) @@ -280,6 +282,16 @@ function edit_commit(inputfile, user_config)
return fallback_edit_commit
end
# Default to DefaultFlavor() setting
pick_codefence(flavor::AbstractFlavor,execute::Bool,name::AbstractString)=pick_codefence(DefaultFlavor(),execute,name)
pick_codefence(flavor::DefaultFlavor,execute::Bool,name::AbstractString)=("````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 ?
error("QuartoFlavor does not support argument execute=true!") : ("```{julia}" => "```")
)
function create_configuration(inputfile; user_config, user_kwargs, type=nothing)
# Combine user config with user kwargs
user_config = Dict{String,Any}(string(k) => v for (k, v) in user_config)
@ -312,10 +324,11 @@ function create_configuration(inputfile; user_config, user_kwargs, type=nothing) @@ -312,10 +324,11 @@ function create_configuration(inputfile; user_config, user_kwargs, type=nothing)
cfg["mdstrings"] = false
cfg["keep_comments"] = false
cfg["execute"] = type === :md ? false : true
cfg["codefence"] = get(user_config, "flavor", cfg["flavor"]) isa DocumenterFlavor &&
!get(user_config, "execute", cfg["execute"]) ?
("````@example $(get(user_config, "name", replace(cfg["name"], r"\s" => "_")))" => "````") :
("````julia" => "````")
cfg["codefence"] = pick_codefence(
get(user_config, "flavor", cfg["flavor"]),
get(user_config, "execute", cfg["execute"]),
get(user_config, "name", replace(cfg["name"], r"\s" => "_"))
)
cfg["image_formats"] = _DEFAULT_IMAGE_FORMATS
cfg["edit_commit"] = edit_commit(inputfile, user_config)
deploy_branch = "gh-pages" # TODO: Make this configurable like Documenter?
@ -444,7 +457,7 @@ function preprocessor(inputfile, outputdir; user_config, user_kwargs, type) @@ -444,7 +457,7 @@ function preprocessor(inputfile, outputdir; user_config, user_kwargs, type)
mkpath(outputdir)
outputdir = realpath(abspath(outputdir))
isdir(outputdir) || error("not a directory: $(outputdir)")
ext = type === (:nb) ? ".ipynb" : ".$(type)"
ext = type === (:nb) ? ".ipynb" : (type === (:md) && config["flavor"] isa QuartoFlavor) ? ".qmd" : ".$(type)"
outputfile = joinpath(outputdir, config["name"]::String * ext)
if inputfile == outputfile
throw(ArgumentError("outputfile (`$outputfile`) is identical to inputfile (`$inputfile`)"))

37
test/runtests.jl

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
import Literate, JSON
import Literate: Chunk, MDChunk, CodeChunk
import Literate: pick_codefence
using Test
# compare content of two parsed chunk vectors
@ -201,6 +202,7 @@ content = """ @@ -201,6 +202,7 @@ content = """
Source code only #src
## # Comment
## another comment
##| echo: false Quarto parameters
#-
for i in 1:10
print(i)
@ -316,6 +318,7 @@ const GITLAB_ENV = Dict( @@ -316,6 +318,7 @@ const GITLAB_ENV = Dict(
x + 3
# # Comment
# another comment
#| echo: false Quarto parameters
for i in 1:10
print(i)
@ -548,6 +551,7 @@ end end @@ -548,6 +551,7 @@ end end
x * 3
# # Comment
# another comment
#| echo: false Quarto parameters
````
````@example inputfile; continued = true
@ -732,6 +736,19 @@ end end @@ -732,6 +736,19 @@ end end
@test !occursin("EditURL", markdown)
@test !occursin("#hide", markdown)
# flavor = QuartoFlavor()
# execution of Quarto markdown is not allowed
let expected_error = ErrorException("QuartoFlavor does not support argument execute=true!")
@test_throws expected_error Literate.markdown("quarto.jl",flavor=Literate.QuartoFlavor(),execute=true)
end
Literate.markdown(inputfile, outdir, flavor = Literate.QuartoFlavor(),execute=false)
markdown = read(joinpath(outdir, "inputfile.qmd"), String)
@test occursin("```{julia}", markdown)
@test !occursin(r"`{3,}@example", markdown)
@test !occursin("continued = true", markdown)
@test !occursin("EditURL", markdown)
@test !occursin("#hide", markdown)
# documenter = false (deprecated)
@test_deprecated r"The documenter=true keyword to Literate.markdown is deprecated" begin
Literate.markdown(inputfile, outdir, documenter = true)
@ -859,6 +876,11 @@ end end @@ -859,6 +876,11 @@ end end
@test occursin("# MD", markdown) # text/markdown
@test occursin("~~~\n<h1>MD</h1>\n~~~", markdown) # text/html
# QuartoFlavor file extension
write(inputfile, "#=\r\nhello world\n=#\r\n")
_, config = Literate.preprocessor(inputfile, outdir; user_kwargs=(), user_config=Dict("flavor"=>Literate.QuartoFlavor()), type=:md)
@test config["literate_ext"] == ".qmd"
# verify that inputfile exists
@test_throws ArgumentError Literate.markdown("nonexistent.jl", outdir)
@ -955,7 +977,8 @@ end end @@ -955,7 +977,8 @@ end end
"x * 3\\n",
"x * 3\\n",
"# # Comment\\n",
"# another comment"
"# another comment\\n",
"#| echo: false Quarto parameters"
],
""",
@ -1317,6 +1340,18 @@ end end @@ -1317,6 +1340,18 @@ end end
@test occursin("Link to nbviewer: www.example2.com/file.jl", script)
@test occursin("Link to binder: www.example3.com/file.jl", script)
# Test pick_codefence function
default_codefence=pick_codefence(Literate.DefaultFlavor(),true,"testname")
@test default_codefence == ("````julia" => "````")
@test default_codefence == pick_codefence(Literate.FranklinFlavor(),true,"testname")
@test default_codefence == pick_codefence(Literate.DocumenterFlavor(),true,"testname")
documenter_codefence = ("````@example testname" => "````")
@test documenter_codefence == pick_codefence(Literate.DocumenterFlavor(),false,"testname")
let expected_exception=ErrorException("QuartoFlavor does not support argument execute=true!")
@test_throws expected_exception pick_codefence(Literate.QuartoFlavor(),true,"testname")
end
@test ("```{julia}" => "```") == pick_codefence(Literate.QuartoFlavor(),false,"testname")
# Misc default configs
create(; type, kw...) = Literate.create_configuration(inputfile; user_config=Dict(), user_kwargs=kw, type=type)
cfg = create(; type=:md, execute=true)

Loading…
Cancel
Save