Skip to content

Commit

Permalink
Adds a concept of "flavors" for different output. (#156)
Browse files Browse the repository at this point in the history
Adds `Literate.FranklinFlavor` that supports text/html output
when executing the markdown file.

fixes #146, closes #147
  • Loading branch information
fredrikekre committed Jul 8, 2021
1 parent 8b2d179 commit 79615c9
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 3 deletions.
15 changes: 15 additions & 0 deletions docs/src/outputformats.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ behavior and resulting output of [`Literate.markdown`](@ref).
Literate.markdown
```

### Markdown flavors

Literate supports different "flavors" to the markdown output. To specify a flavor pass the
`flavor` keyword argument to [`Literate.markdown`](@ref). The default flavor
(`Literate.DefaultFlavor`) is the Documenter compatible output used throughout most examples.
Another supported flavor is `Literate.FranklinFlavor` for output compatible with the
[Franklin.jl](https://franklinjl.org/) static site generator. Currently the only difference
is that the Franklin flavor supports `text/html` MIME output when executing the markdown
file, e.g. with
```julia
Literate.markdown(input, output; execute=true, flavor=Literate.FranklinFlavor())
```
!!! compat "Literate 2.9"
The Franklin flavor requires at least Literate version 2.9.

## [**4.2.** Notebook Output](@id Notebook-Output)

Notebook output is generated by [`Literate.notebook`](@ref). The (default) notebook output
Expand Down
21 changes: 19 additions & 2 deletions src/Literate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import JSON, REPL, IOCapture
include("IJulia.jl")
import .IJulia

abstract type AbstractFlavor end
struct DefaultFlavor <: AbstractFlavor end

# # Some simple rules:
#
# * All lines starting with `# ` are considered markdown, everything else is considered code
Expand Down Expand Up @@ -226,6 +229,7 @@ function create_configuration(inputfile; user_config, user_kwargs, type=nothing)
cfg["execute"] = type === :md ? false : true
cfg["codefence"] = get(user_config, "documenter", true) && !get(user_config, "execute", cfg["execute"]) ?
("````@example $(get(user_config, "name", cfg["name"]))" => "````") : ("````julia" => "````")
cfg["flavor"] = DefaultFlavor()
# Guess the package (or repository) root url
edit_commit = "master" # TODO: Make this configurable like Documenter?
deploy_branch = "gh-pages" # TODO: Make this configurable like Documenter?
Expand Down Expand Up @@ -297,6 +301,7 @@ See the manual section about [Configuration](@ref) for more information.
| `keep_comments` | When `true`, keeps markdown lines as comments in the output script. | `false` | Only applicable for `Literate.script`. |
| `execute` | Whether to execute and capture the output. | `true` (notebook), `false` (markdown) | Only applicable for `Literate.notebook` and `Literate.markdown`. For markdown this requires at least Literate 2.4. |
| `codefence` | Pair containing opening and closing fence for wrapping code blocks. | `````"````julia" => "````"````` | If `documenter` is `true` the default is `````"````@example"=>"````"`````. |
| `flavor` | Output flavor for markdown. | `Literate.DefaultFlavor()` | See [Markdown flavors](@ref). |
| `devurl` | URL for "in-development" docs. | `"dev"` | See [Documenter docs](https://juliadocs.github.io/Documenter.jl/). Unused if `repo_root_url`/`nbviewer_root_url`/`binder_root_url` are set. |
| `repo_root_url` | URL to the root of the repository. | - | Determined automatically on Travis CI, GitHub Actions and GitLab CI. Used for `@__REPO_ROOT_URL__`. |
| `nbviewer_root_url` | URL to the root of the repository as seen on nbviewer. | - | Determined automatically on Travis CI, GitHub Actions and GitLab CI. Used for `@__NBVIEWER_ROOT_URL__`. |
Expand Down Expand Up @@ -410,6 +415,10 @@ function script(inputfile, outputdir=pwd(); config::Dict=Dict(), kwargs...)
return outputfile
end


# struct Documenter <: Flavor end # TODO: Use this instead of documenter=true?
struct FranklinFlavor <: AbstractFlavor end

"""
Literate.markdown(inputfile, outputdir=pwd(); config::Dict=Dict(), kwargs...)
Expand Down Expand Up @@ -459,7 +468,8 @@ function markdown(inputfile, outputdir=pwd(); config::Dict=Dict(), kwargs...)
write_code = !(all(l -> endswith(l, "#hide"), chunk.lines) && !(config["documenter"]::Bool))
write_code && write(iomd, seekstart(iocode))
if config["execute"]::Bool
execute_markdown!(iomd, sb, join(chunk.lines, '\n'), outputdir; inputfile=config["literate_inputfile"])
execute_markdown!(iomd, sb, join(chunk.lines, '\n'), outputdir;
inputfile=config["literate_inputfile"], flavor=config["flavor"])
end
end
write(iomd, '\n') # add a newline between each chunk
Expand All @@ -473,13 +483,20 @@ function markdown(inputfile, outputdir=pwd(); config::Dict=Dict(), kwargs...)
return outputfile
end

function execute_markdown!(io::IO, sb::Module, block::String, outputdir; inputfile::String="<unknown>")
function execute_markdown!(io::IO, sb::Module, block::String, outputdir;
inputfile::String="<unknown>", flavor::AbstractFlavor)
# TODO: Deal with explicit display(...) calls
r, str, _ = execute_block(sb, block; inputfile=inputfile)
# issue #101: consecutive codefenced blocks need newline
# issue #144: quadruple backticks allow for triple backticks in the output
plain_fence = "\n````\n" => "\n````"
if r !== nothing && !REPL.ends_with_semicolon(block)
if flavor isa FranklinFlavor && showable(MIME("text/html"), r)
write(io, "\n~~~\n")
Base.invokelatest(show, io, MIME("text/html"), r)
write(io, "\n~~~\n")
return
end
for (mime, ext) in [(MIME("image/png"), ".png"), (MIME("image/jpeg"), ".jpeg")]
if showable(mime, r)
file = string(hash(block) % UInt32) * ext
Expand Down
10 changes: 9 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,8 @@ end end
#-
struct MD end
Base.show(io::IO, mime::MIME"text/markdown", ::MD) = print(io, "# " * "MD")
Base.show(io::IO, mime::MIME"text/html", ::MD) =
print(io, "<h1>" * "MD" * "</h1>")
MD()
#-
print("hello"); print(stdout, ", "); print(stderr, "world")
Expand All @@ -730,12 +732,18 @@ end end
@test occursin("```\n2×2 $(Matrix{Int}):\n 1 2\n 3 4\n```", markdown) # text/plain
@test occursin(r"!\[\]\(\d+\.png\)", markdown) # image/png
@test occursin(r"!\[\]\(\d+\.jpeg\)", markdown) # image/jpeg
@test occursin(r"# MD", markdown) # text/markdown
@test occursin("# MD", markdown) # text/markdown
@test !occursin("~~~\n<h1>MD</h1>\n~~~", markdown) # text/html
@test occursin("```\nhello, world\n```", markdown) # stdout/stderr
@test occursin("```\n42\n```", markdown) # result over stdout/stderr
@test !occursin("246", markdown) # empty output because trailing ;
@test !occursin("```\nnothing\n```", markdown) # empty output because nothing as return value
@test occursin("```\nhello there\n```", markdown) # nothing as return value, non-empty stdout
# FranklinFlavor
Literate.markdown(inputfile, outdir; execute=true, flavor=Literate.FranklinFlavor())
markdown = read(joinpath(outdir, "inputfile.md"), String)
@test !occursin("# MD", markdown) # text/markdown
@test occursin("~~~\n<h1>MD</h1>\n~~~", markdown) # text/html

# verify that inputfile exists
@test_throws ArgumentError Literate.markdown("nonexistent.jl", outdir)
Expand Down

0 comments on commit 79615c9

Please sign in to comment.