# comment to fix https://github.com/r-lib/pkgdown/issues/1843 knitr::opts_chunk$set( collapse = TRUE, compact = TRUE, comment = "#>" ) # setup the lesson to include a child document library("sandpaper") library("pegboard") snd <- asNamespace("sandpaper") # do not use the package cache because it will slow us down no_package_cache() suppressMessages(lsn <- create_lesson(tempfile(), open = FALSE)) # Create the child file ----------------------------------------- txt <- c("## Session Information\n", "The following is the session information of this R session", "\n```r", "sessionInfo()", "```\n") writeLines(txt, fs::path(lsn, "episodes", "files", "child.Rmd")) # Append the episode -------------------------------------------- ep <- Episode$new(fs::path(lsn, "episodes", "introduction.Rmd")) n <- length(xml2::xml_children(ep$body)) # we want to add the child just before the keypoints, which consist of the # last three paragraphs ep$add_md("```r\n```\n", where = n - 3) ep$write(fs::path(lsn, "episodes"), "Rmd") # Load the lesson object ---------------------------------------- lsn_obj <- snd$this_lesson(lsn)
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.
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:
files/
folder under their parent foldersFor example, you have a file structure like this:
withr::with_dir(lsn, fs::dir_tree("./episodes/"))
The last few lines of episodes/introduction.Rmd
looks like this:
```r lsn_obj$episodes[[1]]$tail(15) ```
You can see there the declaration of the child document is an empty code chunk:
```r ```
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:
```r lsn_obj$children[[1]]$show() ```
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:
```r snd$build_markdown(lsn, rebuild = FALSE, quiet = TRUE, slug = "introduction") lsn_obj$load_built() lsn_obj$built[[1]]$tail(47) ```
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:
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.
```r ep <- lsn_obj$episodes[[1]] txt <- c( "```r", "writeLines(format(Sys.time()), 'data/time.txt')", "cat(paste0('The time is: ', readLines('data/time.txt')))", "```" ) ep$add_md(txt, 1) ep$write(fs::path(lsn, "episodes"), format = "Rmd") ep$head(10) ```
When the lesson gets built, the episodes/
directory will continue to like
this:
snd$build_markdown(lsn, rebuild = FALSE, quiet = TRUE) withr::with_dir(lsn, fs::dir_tree("./episodes/"))
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:
withr::with_dir(lsn, fs::dir_tree("./site/built/"))
You can see in data/
, it now contains time.txt
. The rendered Markdown file
site/built/introduction.md
reflects the updates:
```r lsn_obj$load_built() lsn_obj$built[["site/built/introduction.md"]]$head(10) ```
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
.
lsn_obj <- snd$this_lesson(lsn) withr::with_dir(fs::path(lsn, "episodes", "files"), { fs::dir_create("children") fs::file_touch("../fig/one.png") writeLines(c( "---", "title: info", "---", "\nthis is a test file\n" ), con = "../../learners/info.md" ) fs::file_move("child.Rmd", "children/child.Rmd") }) # setup the child to use the correct code. withr::with_dir(lsn, fs::dir_tree("./episodes/")) ep <- lsn_obj$episodes[[1]] code <- ep$code xml2::xml_set_attr(code[[5]], "child", sQuote("files/children/child.Rmd", q = 2) ) ep$write(fs::path(lsn, "episodes"), format = "Rmd") lsn_obj <- snd$this_lesson(lsn) lsn_obj$children[[1]]$add_md(" Here is a link to [the info document](../learners/info.md) {alt='example figure'} ") lsn_obj$children[[1]]$write(fs::path(lsn, "episodes/files/children"), format = "Rmd" )
The parent document (episodes/introduction.Rmd
) referencing the child document
would look like this:
```r lsn_obj$episodes[[1]]$tail(15) ```
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:
```r lsn_obj$children[[1]]$show() ```
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
directory[^1]\:
[^1]: there is a caveat to this paradigm, on translation to HTML, all links
that begin with ../[folder]/
prefixes have those stripped in the final
version of the site.
snd$build_markdown(lsn, rebuild = TRUE, quiet = TRUE) withr::with_dir(lsn, fs::dir_tree("./site/built/"))
Which makes sense in site/built/introduction.md
:
```r lsn_obj$load_built() lsn_obj$built[["site/built/introduction.md"]]$tail(50) ```
If you use relative links in the child document, you will find warnings about missing files during validation:
child <- lsn_obj$children[[1]] prepend_dest <- function(nodes, new = "../../") { dst <- xml2::xml_attr(nodes, "destination") xml2::xml_set_attr(nodes, "destination", paste0(new, dst)) } prepend_dest(child$links) prepend_dest(child$images) child$write(fs::path(lsn, "episodes/files/children"), format = "Rmd") snd$clear_this_lesson() validate_lesson(lsn)
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 ```
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.