384 lines
13 KiB
Markdown
384 lines
13 KiB
Markdown
|
@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:
|
|||
|
|
|||
|
`````plaintext
|
|||
|
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,
|
|||
|
1. 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`
|
|||
|
1. 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/`)
|
|||
|
1. _relative to the assets dir_: `p/script` will write the code block to `/assets/p/script.jl`
|
|||
|
1. _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
|
|||
|
|
|||
|
```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:
|
|||
|
|
|||
|
```julia
|
|||
|
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:
|
|||
|
|
|||
|
```julia:ex_show
|
|||
|
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
|
|||
|
|
|||
|
```julia-repl
|
|||
|
(1.x) pkg> activate .
|
|||
|
(myWebsite) pkg> add PyCall
|
|||
|
```
|
|||
|
|
|||
|
once that's done, if you now start the server, Franklin will write
|
|||
|
|
|||
|
```julia-repl
|
|||
|
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](https://alan-turing-institute.github.io/MLJTutorials/)).
|
|||
|
|
|||
|
### 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
|
|||
|
|
|||
|
`````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:
|
|||
|
|
|||
|
```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}
|
|||
|
|
|||
|
**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](#more_on_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,
|
|||
|
1. 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.
|
|||
|
|
|||
|
```julia
|
|||
|
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`
|
|||
|
|
|||
|
```julia
|
|||
|
print("hello")
|
|||
|
```
|
|||
|
|
|||
|
Then `genplain("script1.jl")` will generate `/assets/scripts/output/script1.out` with content
|
|||
|
|
|||
|
```julia
|
|||
|
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.
|