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():

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

Next, ensure that you call using Books; gen(; M), where M = YourModule. 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

Writing output of `M.homepage_intro()`
Updating html

Calling gen will place the text

This book is built with Julia 1.7.3.

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

`jl 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.7.3.

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 Books; using MyPackage; M = 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"], [M])

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

mygen() = gen("my_text")
entr(mygen, ["contents"], [M])

With this, mygen will be called every time something changes in one of the files in the contents folder or when something changes in your module M. Note that you have to 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 your module M.

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


Table 2

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