Skip to contents

Introduction

Writing a lesson using The Workbench consists of writing your lesson in markdown or R Markdown files, placing them in the episodes/ directory and defining the order of those files in the config.yaml file. If you want to author a lesson that contains content that is repeated across episodes, before {sandpaper} version 0.14, you had to copy and paste the content across these documents, which would be frustrating to update down the line. This is where child documents come in. You can save a repeated snippet of markdown inside of a separate file and have R Markdown render the parent as if it had that snippet embedded the whole time.

The only difference between child documents and the regular episodic content is that these documents are not independent episodes in their own right. They do not need to have a title, they do not need to be listed in the config.yaml, they do not need any callout blocks. They only exist to be used by other markdown documents. Because of this, it is NOT recommended to make these documents overly complex.

An Example: One Child Document

You can define these using the child attribute in code chunks in R Markdown files (note: this requires R Markdown). You can use as many child documents as you wish, but when you create child files, be sure the following two rules apply:

  1. The child documents live in the files/ folder under their parent folders
  2. The reference to child files is relative to the parent file.

For example, you have a file structure like this:

# ./episodes/
# ├── data
# ├── fig
# ├── files
# │   └── child.Rmd
# └── introduction.Rmd

The last few lines of episodes/introduction.Rmd looks like this:



Cool, right?

```{r si, child="files/child.Rmd"}
```

::::::::::::::::::::::::::::::::::::: keypoints

- Use `.md` files for episodes when you want static content
- Use `.Rmd` files for episodes when you need to generate output
- Run `sandpaper::check_lesson()` to identify any issues with your lesson
- Run `sandpaper::build_lesson()` to preview your lesson locally

::::::::::::::::::::::::::::::::::::::::::::::::

You can see there the declaration of the child document is an empty code chunk:


```{r si, child = "files/child.Rmd"}
```

This tells {knitr} to take a detour and render the contents of files/child.Rmd(relative to the current working directory, which is the episodes folder). The entire document of episodes/files/child.Rmd looks like this:


## Session Information

The following is the session information of this R session

```{r sessioninfo}
sessionInfo()
```

This means that when episodes/introduction.Rmd is built, episodes/files/child.Rmd will also be built, evaulating the sessionInfo(). So the resulting markdown document will look like this:


`$\alpha = \dfrac{1}{(1 - \beta)^2}$` becomes: $\\alpha = \\dfrac{1}{(1 - \\beta)^2}$

Cool, right?

## Session Information

The following is the session information of this R session

```r
sessionInfo()
```

```output
R version 4.3.3 (2024-02-29)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 22.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0

locale:
 [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
 [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
 [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
[10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   

time zone: UTC
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] compiler_4.3.3 tools_4.3.3    highr_0.10     knitr_1.46     xfun_0.43     
[6] evaluate_0.23 
```

::::::::::::::::::::::::::::::::::::: keypoints

- Use `.md` files for episodes when you want static content
- Use `.Rmd` files for episodes when you need to generate output
- Run `sandpaper::check_lesson()` to identify any issues with your lesson
- Run `sandpaper::build_lesson()` to preview your lesson locally

::::::::::::::::::::::::::::::::::::::::::::::::

Workspace consideration

The {sandpaper} machinery aims to keep the working space as clean as possible while still allowing authors to write in a way that feels natural. During processing, assets (all .R files, everything in files/, fig/, and data/) are copied over from their source folders to site/built/. This allows us to build the R Markdown files using site/built/ as the working directory which accomplishes two things:

  1. The default sandpaper folder structure is flattened for the website.
  2. any files the R Markdown document creates or deletes will not affect the source files of the project

For example, let’s say we added a chunk to our source R Markdown folder that will create a file in the data/ directory called “time.txt” and then reads in the contents of that file.


---
title: 'introduction'
teaching: 10
exercises: 2
---

```{r setup, echo=FALSE}
writeLines(format(Sys.time()), 'data/time.txt')
cat(paste0('The time is: ', readLines('data/time.txt')))
```

When the lesson gets built, the episodes/ directory will continue to like this:

# ./episodes/
# ├── data
# ├── fig
# ├── files
# │   └── child.Rmd
# └── introduction.Rmd

Notice that there is no file called data/time.txt. This is because that file was built inside the site/built directory. The contents of this directory contains the output along with all of the other files in the lesson:

# ./site/built/
# ├── CODE_OF_CONDUCT.md
# ├── LICENSE.md
# ├── config.yaml
# ├── data
# │   └── time.txt
# ├── fig
# │   └── introduction-rendered-pyramid-1.png
# ├── files
# │   └── child.Rmd
# ├── index.md
# ├── instructor-notes.md
# ├── introduction.md
# ├── learner-profiles.md
# ├── links.md
# ├── md5sum.txt
# ├── reference.md
# └── setup.md

You can see in data/, it now contains time.txt. The rendered Markdown file site/built/introduction.md reflects the updates:


---
title: 'introduction'
teaching: 10
exercises: 2
---

```output
The time is: 2024-04-19 13:11:29
```

Child document assets

When you use a child file, it is best to avoid relying on assets external to the child document itself. If you do decide to reference other assets, child documents need to have assets written as if they were already embedded in the build parent. In this case, when I say build parent, I am refering to the final parent of the child document where the output will eventually end up.

Let’s say you wanted to refererence a figure episodes/fig/one.png and a link to a document for learners learners/info.md from the child document, but instead it’s located at episodes/files/children/child.Rmd, which is built by episodes/introduction.Rmd.

# ./episodes/
# ├── data
# ├── fig
# │   └── one.png
# ├── files
# │   └── children
# │       └── child.Rmd
# └── introduction.Rmd

The parent document (episodes/introduction.Rmd) referencing the child document would look like this:



Cool, right?

```{r si, child="files/children/child.Rmd"}
```

::::::::::::::::::::::::::::::::::::: keypoints

- Use `.md` files for episodes when you want static content
- Use `.Rmd` files for episodes when you need to generate output
- Run `sandpaper::check_lesson()` to identify any issues with your lesson
- Run `sandpaper::build_lesson()` to preview your lesson locally

::::::::::::::::::::::::::::::::::::::::::::::::

In episodes/files/children/child.Rmd, under internal link rules, you you should reference these resources with fig/one.png and ../learners/info.md, which are in the context of the build parent directory (in this case, episodes/), as demonstrated here:


Here is a link to [the info document](../learners/info.md)

![example](fig/one.png){alt='example figure'}

## Session Information

The following is the session information of this R session

```{r sessioninfo}
sessionInfo()
```

Again, the reason for this is because when the document gets built, the result is all in the context of the parent document in the site/built directory1:

# ./site/built/
# ├── CODE_OF_CONDUCT.md
# ├── LICENSE.md
# ├── config.yaml
# ├── data
# │   └── time.txt
# ├── fig
# │   ├── introduction-rendered-pyramid-1.png
# │   └── one.png
# ├── files
# │   └── children
# │       └── child.Rmd
# ├── index.md
# ├── info.md
# ├── instructor-notes.md
# ├── introduction.md
# ├── learner-profiles.md
# ├── links.md
# ├── md5sum.txt
# ├── reference.md
# └── setup.md

Which makes sense in site/built/introduction.md:



Cool, right?

Here is a link to [the info document](../learners/info.md)

![example](fig/one.png){alt='example figure'}

## Session Information

The following is the session information of this R session

```r
sessionInfo()
```

```output
R version 4.3.3 (2024-02-29)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 22.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0

locale:
 [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
 [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
 [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
[10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   

time zone: UTC
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] compiler_4.3.3 tools_4.3.3    highr_0.10     knitr_1.46     xfun_0.43     
[6] evaluate_0.23 
```

::::::::::::::::::::::::::::::::::::: keypoints

- Use `.md` files for episodes when you want static content
- Use `.Rmd` files for episodes when you need to generate output
- Run `sandpaper::check_lesson()` to identify any issues with your lesson
- Run `sandpaper::build_lesson()` to preview your lesson locally

::::::::::::::::::::::::::::::::::::::::::::::::

If you use relative links in the child document, you will find warnings about missing files during validation:

#> ── Validating Fenced Divs ──────────────────────────────────────────────
#> ── Validating Internal Links and Images ────────────────────────────────
#> ! There were errors in 2/39 links and images
#> ◌ Some linked internal files do not exist <https://carpentries.github.io/sandpaper/articles/include-child-documents.html#workspace-consideration>
#> 
#> ::warning file=episodes/files/children/child.Rmd,line=1:: [missing file (relative to episodes/)]: [the info document](../../../learners/info.md)
#> ::warning file=episodes/files/children/child.Rmd,line=3:: [missing file (relative to episodes/)]: [example](../../fig/one.png)

Child documents of child documents

It is also possible to reference other child documents within the child document, but again, these child documents paths must be relative to the build parent path

Thus, if you have a child document calling a second child document, you must write the relative path from the build parent. This means that if you want to reference episodes/files/child-two.md from episodes/files/child-one.Rmd, you must write it this way: in episodes/files/child-one.Rmd:


```{r child='files/child-two.md'}
```