3.1 Embedding output

For embedding code, you can use the jl inline code or code block. For example, to show the Julia version, define a code block like


in a Markdown file. Then, in your package, define the method julia_version():

julia_version() = "This book is built with Julia $VERSION."

Next, call using Books, MyPackage and gen() to run all the defined in the Markdown files. If you prefer to be less explicit, you can call gen(; M=YourModule) to allow for:


instead of YourModule.julia_version(). When passing your module M as keyword argument, Books.jl will evaluate all code blocks inside that module.

Alternatively, if you work on a large project and want to only generate the output for one or more Markdown files in contents/, such as index.md, use


Calling gen will place the text

This book is built with Julia 1.10.0.

at the right path so that it can be included by Pandoc. You can also embed output inline with single backticks like

`jl YourModule.julia_version()`

or just call Julia’s constant VERSION directly from within the Markdown file. For example,

This book is built with Julia `jl VERSION`.

This book is built with Julia 1.10.0.

While doing this, it is expected that you also have the browser open and a server running, see Section 2. That way, the page is immediately updated when you run gen.

Note that it doesn’t matter where you define the function julia_version, as long as it is in your module. To save yourself some typing, and to allow yourself to get some coffee while Julia gets up to speed, you can start Julia for your package with

$ julia --project -ie 'using MyPackage'

which allows you to re-generate all the content by calling

julia> gen()

To run this method automatically when you make a change in your package, ensure that you loaded Revise.jl before loading your package and run

entr(gen, ["contents"], [MyPackage])

Which will automatically run gen() whenever one of the files in contents/ changes or any code in the MyPackage module. To only run gen for one file, such as contents/my_text.md, use:

entr(() -> gen("my_text"), ["contents"], [MyPackage])

Or, the equivalent helper function exported by Books.jl:

entr_gen("my_text"; M=[MyPackage])

With this, gen("my_text") will be called every time something changes in one of the files in the contents folder or when something changes in YourModule. Note that you can run this while serve is running in another terminal in the background. Then, your Julia code is executed and the website is automatically updated every time you change something in content or MyPackage. Also note that gen is a drop-in replacement for entr_gen, so you can always add or remove entr_ to run a block one time or multiple times.

In the background, gen passes the methods through convert_output(expr::String, path, out::T) where T can, for example, be a DataFrame or a plot. To show that a DataFrame is converted to a Markdown table, we define a method

my_table() = DataFrame(U = [1, 2], V = [:a, :b], W = [3, 4])

and add its output to the Markdown file with


Then, it will show as

Table 2: My table.
1 a 3
2 b 4

where the caption and the label are inferred from the path. Refer to Table 2 with


To show multiple objects, pass a Vector:

function multiple_df_vector()
    [DataFrame(Z = [3]), DataFrame(U = [4, 5], V = [6, 7])]
4 6
5 7

When you want to control where the various objects are saved, use Options. This way, you can pass a informative path with plots for which informative captions, cross-reference labels and image names can be determined.

function multiple_df_example()
    objects = [
        DataFrame(X = [3, 4], Y = [5, 6]),
        DataFrame(U = [7, 8], V = [9, 10])
    filenames = ["a", "b"]
    Options.(objects, filenames)
Table 3: A.
3 5
4 6
Table 4: B.
7 9
8 10

To define the labels and/or captions manually, see Section 3.2. For showing multiple plots, see Section 3.4.

Most things can be done via functions. However, defining a struct is not possible, because @sco cannot locate the struct definition inside the module. Therefore, it is also possible to pass code and specify that you want to evaluate and show code (sc) without showing the output:

s = """
    struct Point

which shows as

struct Point

and show code and output (sco). For example,

sco("p = Point(1, 2)")

shows as

p = Point(1, 2)
Point(1, 2)

Note that this is starting to look a lot like R Markdown where the syntax would be something like

```{r, results='hide'}
x = rnorm(100)

I guess that there is no perfect way here. The benefit of evaluating the user input directly, as Books.jl is doing, seems to be that it is more extensible if I’m not mistaken. Possibly, the reasoning is that R Markdown needs to convert the output directly, whereas Julia’s better type system allows for converting in much later stages, but I’m not sure.

Tip: When using sco, the code is evaluated in the Main module. This means that the objects, such as the Point struct defined above, are available in your REPL after running gen().

CC BY-NC-SA 4.0 Rik Huijzer, and contributors