Inserting and evaluating code
Franklin makes it easy to insert code and the result of running the code; Julia code can be evaluated on the fly.Overview
Inserting code
As per Common Mark specifications, you have multiple ways of inserting code:
inline code: you can use single backticks (`) or double backticks (``) (if the code contains single ticks) like so:
This is some `inline code` or ``inline ` code with a tick``.
Note: To use a backslash (\
) at the end of the inline code, you must include a trailing space to ensure the trailing tick is parsed correctly, eg `some code with \ `
. The trailing space will be trimmed in the rendered html.
code blocks: it is recommended to use triple backticks (```) optionally followed by a language name for highlighting like so:
This is some julia code:
```julia
a = 2
@show a
```
code blocks 2: you can also use indented code blocks (lines starting with four spaces or a tab) but fenced code blocks should be preferred and you now have to opt-in to use them by setting
@def indented_code = true
This is some code:
a = 2
@show a
Note: when either using indented code blocks or using fenced code blocks with no language name, then the code language for highlighting can be specified with the local page variable lang
i.e. @def lang = "julia"
(which is the default) or @def lang = ""
if you don't want the code to be highlighted.
Evaluating code
When presenting code in a post, it's often convenient to have a way to check that the code works and that the output shown corresponds to the code. In Franklin, there are two approaches that help you with it:
For Julia code, a live-evaluation of code blocks is supported,
For all languages, you can run the script separately and use Franklin to insert the code file and/or the output generated by the code.
Note: Disabling code evaluation (e.g., when presenting incomplete code snippets in tutorials or courses that would error upon evaluation), can be specified with the local page variable noeval = true
(default false
).
Live evaluation (Julia)
Julia code blocks can be evaluated on the fly and their output is either displayed as code or re-interpreted as Markdown.
Code blocks that should not be evaluated should be added as per standard markdown, so for instance:
```julia
a = 10
```
Code blocks that should be evaluated should be added with julia:path/to/script
where path/to/script
indicates where the script corresponding to the code block will be saved (note: the given path must be in UNIX format even if you're on Windows)
```julia:./code/ex1
a = 10
@show a
```
What this will do is:
write the code to the file
/assets/[subpath]/code/ex1.jl
run the code and capture its output (
STDOUT
) and write it to/assets/[subpath]/code/output/ex1.out
The [subpath]
here is the exact same sub-path structure as the page where the code block is inserted. To clarify, let's say you wrote the above code block in
/folder1/page1.md
then with the syntax above, the script will be saved in
/__site/assets/folder1/code/ex1.jl
More on paths
There are three ways you can specify where the script corresponding to a code block should be saved.
relative to the page:
./[p]/script
is as above, it will write the code block to/assets/[subpath]/p/script.jl
wheresubpath
corresponds to the sub-path of the page where the code block is inserted (path below/src/
)relative to the assets dir:
p/script
will write the code block to/assets/p/script.jl
full path:
/p/script
will write the code block to/p/script.jl
Note: when code blocks are evaluated, their output (STDOUT
) is captured and saved at [path]/output/script.out
where [path]
is what precedes script.jl
in the cases above.
Inserting the output
Let's say you've added the following code block:
```julia:./code_pg1/ex1
using LinearAlgebra
a = [1, 2, 3]
@show dot(a, a)
```
In order to show the raw output (whatever was captured in STDOUT) as a code block, write
\output{./code_pg1/ex1}
which in the present example will introduce exactly the following HTML
<pre><code class="language-julia">dot(a, a) = 14</code></pre>
and will look like
dot(a, a) = 14
If you now change the vector a
in the code block, the page will be re-compiled with the code block re-evaluated and the new output will be shown.
If you would like the output to be re-interpeted by Franklin as text, you can use \textoutput
instead. Here's an example:
```julia:./code_pg1/ex2
using Statistics
temps = (15, 15, 14, 16, 18, 19, 20, 12, 10, 24)
println("The _average_ temperature is **$(mean(temps))°C**.")
```
\textoutput{./code_pg1/ex2}
That code block and the \textoutput
command will appear as:
using Statistics
temps = (15, 15, 14, 16, 18, 19, 20, 12, 10, 24)
println("The _average_ temperature is **$(mean(temps))°C**.")
The average temperature is 16.3°C.
Finally if you want to show your code "notebook-style", i.e. both STDOUT and the result of the last line, use \show
:
```julia:ex_show
x = 5
println("hello")
x^2
```
\show{ex_show}
resulting in:
x = 5
println("hello")
x^2
hello
25
Hiding lines
Sometimes you may want to run some lines but hide them from the presentation, for this just use # hide
at the end of the line (hide
is not case sensitive so # HiDe
would be fine too):
```julia:./code_pg1/ex1
using LinearAlgebra # hide
a = [1, 2, 3]
@show dot(a, a)
```
You could also hide the entire code block if you only care about the output, for this put a # hideall
on any line:
```julia:./code_pg1/ex2
#hideall
using Statistics
temps = (15, 15, 14, 16, 18, 19, 20, 12, 10, 24)
println("The _average_ temperature is **$(mean(temps))°C**.")
```
\textoutput{./code_pg1/ex2}
Which will appear as just:
The average temperature is 16.3°C.
Project.toml
It can be convenient to set up your website as you would a Julia environment: activating it and adding the packages that you will use in code blocks. In order to do this, just activate the environment as you would otherwise, this will generate a Project.toml
which will subsequently be used by Franklin without you having to worry about it.
For instance, let's say that you want to use PyCall
in some code blocks, then before starting the Franklin server do
(1.x) pkg> activate .
(myWebsite) pkg> add PyCall
once that's done, if you now start the server, Franklin will write
julia> serve()
Activating environment at `~/Desktop/myWebsite/Project.toml`
In other words, whenever you start the server, Franklin will now activate the environment with that Project.toml
. This is particularly useful if you intend to write a tutorial website (for a live example of this, see the DataScienceTutorials).
Plots
Using the machinery introduced above, you can also evaluate code that generates a plot which you can then include on the page. In the example below, PyPlot
is used but you could do something similar with other frameworks.
Assuming you've added PyPlot
to your environment, this markdown
```julia:pyplot1
using PyPlot
figure(figsize=(8, 6))
x = range(-2, 2, length=500)
for α in 1:5
plot(x, sinc.(α .* x))
end
savefig(joinpath(@OUTPUT, "sinc.svg")) # hide
```
\fig{sinc}
will give:
using PyPlot
figure(figsize=(8, 6))
x = range(-2, 2, length=500)
for α in 1:5
plot(x, sinc.(α .* x))
end
Note: observe that here everything is done with relative paths, pyplot1
is placed in the /assets/
folder relatively to the path of the current page and the \fig
since it's given a path that doesn't start with /
or ./
will also look in that folder to try to find a figure which starts with the name sinc
. See also more about paths.
Note: If you wish to use Plots.jl
and deploy to GitHub pages, you will need to modify the .github/workflows/Deploy.yml
by adding env: GKSwstype: "100"
before the - name: Build and Deploy
line. Here is an example.
Auto and REPL mode
You can use !
and >
to indicate respectively a code that should be run automatically and the output appended immediately after, or the same but with a REPL-style display:
```!
x = 5
y = x^2
```
for instance gives:
x = 5
y = x^2
25
In a similar way:
```>
x = 5
y = x^2
```
gives
julia> x = 5
5
julia> y = x^2
25
Shell, Pkg, Help, these modes are also experimentally supported:
Pkg mode :
```]
st
```
gives
(docs) pkg> st
Status `~/work/Franklin.jl/Franklin.jl/docs/Project.toml`
[713c75ef] Franklin v0.10.95
[f0f68f2c] PlotlyJS v0.18.13
[438e738f] PyCall v1.96.4
[d330b81b] PyPlot v2.11.2
[37e2e46d] LinearAlgebra
[9a3f8284] Random
Shell mode : (note: in a multi-line setting, each line is assumed to be a separate command)
```;
echo "hello!"
date
```
gives
shell> echo "hello!"
"hello!"
shell> date
Tue Apr 16 17:23:39 UTC 2024
Help mode : (note: only single line cell blocks will work properly)
```?
im
```
help?> im
im
The imaginary unit.
See also: imag
, angle
, complex
.
Examples
julia> im * im
-1 + 0im
julia> (2.0 + 3im)^2
-5.0 + 12.0im
Note: for the help
mode above, the output is HTML corresponding to the julia docs, it's wrapped in a julia-help
div which you should style, the above style for instance corresponds to the following CSS:
.julia-help {
background-color: #fffee0;
padding: 10px;
font-style: italic;
}
.julia-help h1,h2,h3 {
font-size: 1em;
font-weight: 500;
}
Troubleshooting
A few things can go wrong when attempting to use and evaluate code blocks. The first thing to do when no output is shown or when an error appears is to make sure that:
if the code uses packages, these packages are available in the local environment,
the code "just works" in the REPL.
If this is the case and you still have issues, then you may want to force a re-evaluation of the code on the page. In such a case, try adding @def reeval = true
on the page which will cause all code blocks on the page to be completely re-evaluated and their output re-generated. Assuming that helped, you will then want to remove that line as otherwise that page will be fully re-evaluated every single time the page is modified which will cause unnecessary overhead.
Important note: unless you explicitly use @def reeval = true
, code blocks are evaluated only if:
an earlier code block has been evaluated (in which case, since their results may depend on it, all subsequent blocks are re-evaluated),
the content of the code block has changed.
An example where this can be a bit tricky is if your code block calls a function on a file, for instance read(file, String)
; if the underlying file is changed, the code block will not be re-evaluated (since the code doesn't change), so in such cases, you will want to use a @def reeval = true
.
Offline evaluation (any language)
The philosophy here is:
keep your code snippets in appropriate subfolders of
/assets/
where they can be run and their output can be saved, this can be compared to atest/
folder in a Julia package,run some or all of the snippets (before running Franklin),
use
\input{...}{...}
in your markdown (see below) and when the website is updated, it will plug in the most recent parts that have been generated.
That way, if you modify the code, everything will be updated on the website too while ensuring that the code actually runs and generates the output you're displaying.
Again, the script files can contain # hide
at the end of lines you do not want to show (#
to be replaced by whatever symbol indicates comments in that language).
The generate_results.jl
file should run the scripts and redirect outputs to the assets/[path]/output
directory. You can use something like the script below (if you generate an example website with newsite
, it's already in there) though you can of course modify it as you wish.
dir = @__DIR__
"""
genplain(s)
Small helper function to run some code and redirect the output (stdout) to a file.
"""
function genplain(s::String)
open(joinpath(dir, "output", "$(splitext(s)[1]).out"), "w") do outf
redirect_stdout(outf) do
include(joinpath(dir, s))
end
end
end
# run `script1.jl` and redirect what it prints to `output/script1.out`
genplain("script1.jl")
# run `script2.jl` which has a savefig(joinpath(@__DIR__, "output", "script2.png"))
include("script2.jl")
The function genplain("scriptname.jl")
just redirects the output of the script to output/scriptname.out
. So for instance if you have in assets/scripts/script1.jl
print("hello")
Then genplain("script1.jl")
will generate /assets/scripts/output/script1.out
with content
hello
R
, Python
, ...) as long as the folder structure is the same.Inserting code
In order to insert the code of a script and have it highlighted you can use
\input{julia}{scripts/script1.jl}
This will insert the content of the file /assets/scripts/script1.jl
(see also the section earlier on paths) into a block that will be highlighted as julia code.
Plain-text output
In order to insert the plain-text output of a script, you can use
\output{scripts/script1.jl}
This will insert the content of the file /assets/scripts/script1.out
into a non-highlighted code block.
Plot output
In order to insert a plot generated by a script, you can use \fig
as indicated earlier or
\input{plot}{scripts/script1.jl}
or \input{plot:id}{scripts/script1.jl}
. This will look for an image file with root name /assets/scripts/script1.ext
where ext
is gif, png, jp(e)g, svg
. If you use plot:id
then it will look for an image file with root name /assets/scripts/script1id.ext
.
The plot:id
option is useful if you have a script that generates several plots for instance.
Slicing up
The structure in the generate_results.jl
effectively means that all your code is run as one big script. This also means that if you want to slice some of your code into several parts and show intermediate outputs (e.g. plots), you can just do that by having a script_1_p1.jl
, script_1_p2.jl
etc. and then just use \input
multiple times.