jwebsite/code/index.md

13 KiB
Raw Blame History

@def hascode = true

Inserting and evaluating code

\blurb{Franklin makes it easy to insert code and the result of running the code; Julia code can be evaluated on the fly.}

\lineskip

\toc

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``.
  • 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 the code works and the output shown corresponds to the code. In Franklin there are two approaches that help you for this:

@@tlist

  1. For Julia code, a live-evaluation of code blocks is supported,
  2. 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. @@

Live evaluation (Julia)

Julia code blocks can be evaluated on the fly and their output either displayed as code or re-interpreted as Markdown.

\note{ Evaluation time: when a code block is created or modified and the page is saved, it will trigger a page build that will wait for the evaluation of the code block to complete. So if your code block takes a long time to execute, the page will not be updated before that's done. That being said, if you don't modify the code block, it will only be executed once as the output is saved to file. }

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:

@@tlist

  1. write the code to the file /assets/[subpath]/code/ex1.jl
  2. 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 than to 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.

@@tlist

  1. relative to the page: ./[p]/script is as above, it will write the code block to /assets/[subpath]/p/script.jl where subpath corresponds to the sub-path of the page where the code block is inserted (path below /src/)
  2. relative to the assets dir: p/script will write the code block to /assets/p/script.jl
  3. 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

\show{ex_show}

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 MLJ Tutorials).

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
savefig(joinpath(@OUTPUT, "sinc.svg")) # hide

\fig{sinc}

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.

Troubleshooting

A few things can go wrong when attempting to use and evaluate code blocks. The first thing to do if no output is shown or an error appears is to make sure that:

@@tlist

  1. if the code uses packages, these packages are available in the local environment,
  2. the code "just works" in the REPL. @@

If this is the case and you still have issues, then you may want to force 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 an unnecessary overhead.

Important note: unless you explicitly use @def reeval = true, code blocks are evaluated only if: @@tlist

  • 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:

@@tlist

  • 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 a test/ 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

\note{You could have scripts in any language here (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 in 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.