Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lesson object vignette #146

Merged
merged 16 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: pegboard
Title: Explore and Manipulate Markdown Curricula from the Carpentries
Version: 0.7.2
Version: 0.7.2.9000
Authors@R: c(
person(given = "Zhian N.",
family = "Kamvar",
Expand Down
14 changes: 14 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# pegboard 0.7.2.9000 (unreleased)

## DOCUMENTATION

* New `vignette("intro-lesson", package = "pegboard")` provides information
about working with `pegboard::Lesson` objects.
* Documentation for `Episode` and `Lesson` objects have been updated to be
a bit more descriptive and point to the vignettes.

## NEW FEATURES

* `Episode$unblock()` method gains the `force` argument which will allow the
method to run even if it has previously been run.

# pegboard 0.7.2 (2023-11-17)

## MISC
Expand Down
22 changes: 18 additions & 4 deletions R/Episode.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@
#'
#' @description
#' Wrapper around an xml document to manipulate and inspect Carpentries episodes
#'
#' @details
#' This class is a fancy wrapper around the results of [tinkr::to_xml()] and
#' has method specific to the Carpentries episodes.
#' The Episode class is a superclass of [tinkr::yarn()], which transforms
#' (commonmark-formatted) Markdown to XML and back again. The extension that
#' the Episode class provides is support for both [Pandoc](https://pandoc.org)
#' and [kramdown](https://kramdown.gettalong.org/) flavours of Markdown.
#'
#' Read more about this class in `vignette("intro-episode", package =
#' "pegboard")`.
#'
#' @export
Episode <- R6::R6Class("Episode",
inherit = tinkr::yarn,
Expand Down Expand Up @@ -99,7 +106,12 @@ Episode <- R6::R6Class("Episode",
self$yaml <- ep$yaml
self$body <- ep$body
self$ns <- ep$ns
# if the parents are missing, this walk will do nothing
purrr::walk(parents, function(parent) add_parent(self, parent))
# the parent here is used to determine the build path for the
# child document, which is dependent on the build parent, aka the final
# ancestor. If there is no parent, then the children are relative to the
# parent.
self$children <- find_children(ep, ancestor = parents[[1]])
},

Expand Down Expand Up @@ -476,6 +488,8 @@ Episode <- R6::R6Class("Episode",
},
#' @description convert challenge blocks to roxygen-like code blocks
#' @param token the token to use to indicate non-code, Defaults to "#'"
#' @param force force the conversion even if the conversion has already
#' taken place
#' @return the Episode object, invisibly
#' @examples
#' loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
Expand All @@ -484,8 +498,8 @@ Episode <- R6::R6Class("Episode",
#' loop$unblock()
#' loop$get_blocks() # no blocks
#' loop$code # now there are two blocks with challenge tags
unblock = function(token = "#'") {
if (private$mutations['unblock']) {
unblock = function(token = "#'", force = FALSE) {
if (!force && private$mutations['unblock']) {
return(invisible(self))
}
if (private$mutations['use_dovetail']) {
Expand Down
13 changes: 10 additions & 3 deletions R/Lesson.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@
#'
#' @description
#' This is a wrapper for several [Episode] class objects.
#'
#' @details
#' Lessons are made of up several episodes within the `_episodes/` directory of
#' a lesson. This class keeps track of several episodes and allows us to switch
#' between RMarkdown and markdown episodes
#'
#' This class contains and keeps track of relationships between [Episode]
#' objects contained within [Carpentries
#' Workbench](https://carpentries.github.io/workbench) and [Carpentries
#' styles](https://carpentries.github.io/lesson-example) lessons.
#'
#' Read more about how to use this class in `vignette("intro-lesson", package =
#' "pegboard")`
#'
#' @export
Lesson <- R6::R6Class("Lesson",
public = list(
Expand Down
62 changes: 32 additions & 30 deletions R/find_children.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#' Detect the child files of an Episode object
#' Detect the child documents of an Episode object
#'
#' @description
#' - `find_children()` returns the _immediate_ children for any given Episode
Expand All @@ -11,7 +11,7 @@
#' @param ancestor an [Episode] object that is used to determine the parent path
#' this also can be `NULL`.
#' @param lsn a [Lesson] object that contains the `parent` and all its children.
#' @return a character vector of the absolute paths to child files.
#' @return a character vector of the absolute paths to child documents
#'
#' @details
#'
Expand Down Expand Up @@ -43,7 +43,7 @@
#' ## Tracing full lineages
#'
#' It is possible for a child document to have further children defined, but
#' there is a caveat: The child file is going to be read from the context of
#' there is a caveat: The child document is going to be read from the context of
#' the `root.dir` knitr option, which in {sandpaper} is set to be `site/built`
#' after the markdown contents and assets are copied over.
#'
Expand All @@ -55,25 +55,25 @@
#' ````
#'
#' When an Episode is read _in the context of a Lesson_, the children are
#' processed with [load_children()] so that each file with children will have a
#' processed with [load_children()] so that each document with children will have a
#' non-zero value of the `$children` element. We recurse through the `$children`
#' element in the [Lesson] object to exhaust the search for the children files.
#' element in the [Lesson] object to exhaust the search for the child documents.
#'
#' The `trace_children()` will return the entire lineage for a given _parent_
#' file. Which, in the case of the examples defined above would be:
#' document. Which, in the case of the examples defined above would be:
#' `/path/to/episodes/parent.Rmd`, `/path/to/episodes/files/the-child.Rmd`,
#' and `/path/to/episodes/files/the-grandchild.md`.
#'
#' ## NOTE
#'
#' For standard lessons, child files are written relative to the directory of
#' the build parent file. Usually, these child files will be in the `files`
#' folder under their parent folder. Overview lessons are a little different.
#' For overview lessons (in The Workbench, these are lessons which contain
#' `overview: true` in config.yaml), the child files may point to
#' `files/child.md`, but in reality, the child file is at the root of the
#' For standard lessons, child documents are written relative to the directory
#' of the build parent document. Usually, these child documents will be in the
#' `files` folder under their parent folder. Overview lessons are a little
#' different. For overview lessons (in The Workbench, these are lessons which
#' contain `overview: true` in config.yaml), the child documents may point to
#' `files/child.md`, but in reality, the child document is at the root of the
#' lesson `../files/child.md`. We correct for this by first checking that the
#' child files exist and if they don't defaulting to the top of the lesson.
#' child documents exist and if they don't defaulting to the top of the lesson.
#'
#' @keywords internal
#' @rdname find_children
Expand All @@ -88,15 +88,15 @@
#' # `find_children()` --------------------------------------------------------
#' ex <- lesson_fragment("sandpaper-fragment-with-child")
#'
#' # The introduction has a single child file
#' # The introduction has a single child document
#' intro <- tinkr::yarn$new(fs::path(ex, "episodes", "intro.Rmd"))
#' intro$head(21) # show the child file
#' intro$head(21) # show the child document
#' pb$find_children(intro)
#' # this is identical to the `$children` element of an Episode object
#' ep <- Episode$new(fs::path(ex, "episodes", "intro.Rmd"))
#' ep$children
#'
#' # Loading the child file reveals another child
#' # Loading the child document reveals another child
#' child <- Episode$new(ep$children[[1]])
#' child$children
#' child$show()
Expand All @@ -116,7 +116,7 @@
#' )
find_children <- function(parent, ancestor = NULL) {
code_blocks <- get_code(parent$body, type = NULL, attr = NULL)
children <- child_file_from_code_blocks(code_blocks)
children <- child_document_from_code_blocks(code_blocks)
any_children <- length(children) > 0L
ancestor_has_parents <- identical(ancestor$has_parents, TRUE)
parent_has_parents <- identical(parent$has_parents, TRUE)
Expand Down Expand Up @@ -160,7 +160,7 @@ find_children <- function(parent, ancestor = NULL) {
return(children)
}
# get the child file from code block if it exists
child_file_from_code_blocks <- function(nodes) {
child_document_from_code_blocks <- function(nodes) {
use_children <- xml2::xml_has_attr(nodes, "child")
if (any(use_children)) {
nodes <- nodes[use_children]
Expand All @@ -183,8 +183,8 @@ child_file_from_code_blocks <- function(nodes) {
}
}

# trace the lineage of a source file and return a recursive list of children
# files. This assumes that the lesson has been set up to process children
# trace the lineage of a source file and return a recursive list of child
# documents. This assumes that the lesson has been set up to process children
#' @rdname find_children
trace_children <- function(parent, lsn) {
if (parent$has_children) {
Expand Down Expand Up @@ -216,24 +216,26 @@ trace_children <- function(parent, lsn) {
#' @details
#'
#' When we want to build lessons, it's important to be able to find all of the
#' files that are necessary to build a particular file. If there is a modification in a child file, \pkg{sandpaper} needs to know that it should flag the parent
#' for rebuilding. To do this, we need two pieces of information:
#' documents that are necessary to build a particular file. If there is a
#' modification in a child document, \pkg{sandpaper} needs to know that it
#' should flag the parent for rebuilding. To do this, we need two pieces of
#' information:
#'
#' 1. The earliest ancestors of a given child file
#' 2. The full list of descendants of a given parent file
#' 1. The earliest ancestors of a given child document.
#' 2. The full list of descendants of a given parent document.
#'
#' Each Episode object only knows about itself, so it can only report its
#' immediate children, but not the children of children, or even its parent
#' (unless we explicitly tell it what its parent is). The [Lesson] object
#' contains the context of all of the Episodes and can provide this information.
#'
#' During Lesson object initialisation, the `load_children()` function is
#' called to process all source files for their children. This creates an
#' called to process all source documents for their children. This creates an
#' empty list of children that is continuously appended to during the function
#' call. It then calls `read_children()` on each parent file, which will append
#' itself as a parent to any existing children in the `all_children` list,
#' intitialize new [Episode] objects from the unread child files, and then
#' search those for children until there are no children left to read.
#' call. It then calls `read_children()` on each parent document, which will
#' append itself as a parent to any existing children in the `all_children`
#' list, intitialize new [Episode] objects from the unread child documents, and
#' then search those for children until there are no children left to read.
#'
#' @keywords internal
#' @seealso [find_children()] for details on how child documents are discovered
Expand Down Expand Up @@ -273,7 +275,7 @@ load_children <- function(all_parents) {
return(the_children)
}

# Read in and/or update all recursive child files for a given parent
# Read in and/or update all recursive child documents for a given parent
#' @rdname load_children
read_children <- function(parent, all_children = list(), ...) {
# if the parent has no children, return NULL. This is the exit condition
Expand Down
37 changes: 29 additions & 8 deletions R/lesson_fragment.R
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
#' An example lesson fragment
#' Example Lesson Fragments
#'
#' This example was taken from the python novice gapminder lesson
#' @param name the name of the lesson fragment
#' Partial lessons mainly used for testing and demonstration purposes
#'
#' @return a path to a lesson fragment that contains one `_episodes` directory
#' with three files: "10-lunch.md", "14-looping-data-sets.md", and
#' "17-scope.md"
#' @note
#' The lesson-fragment example was taken from the python novice gapminder
#' lesson
#'
#' @param name the name of the lesson fragment. Can be one of:
#' - lesson-fragment
#' - rmd-lesson
#' - sandpaper-fragment
#' - sandpaper-fragment with child
#'
#' @return a path to a lesson fragment whose contents are:
#' - `lesson-fragment` contains one `_episodes` directory with three files:
#' "10-lunch.md", "14-looping-data-sets.md", and "17-scope.md"
#' - `rmd-fragment` contains one episode under `_episodes_rmd` called
#' `01-test.Rmd`.
#' - `sandpaper-fragment` contains a trimmed-down Workbench lesson that
#' has its R Markdown content pre-built
#' - `sandpaper-fragment-with-child` contains much of the same content as
#' `sandpaper-fragment`, but the `episodes/index.Rmd` file references child
#' documents.
#' @export
#' @examples
#'
#' lesson_fragment()
#' lesson_fragment("rmd-lesson")
#' lesson_fragment("sandpaper-fragment")
#' lesson_fragment("sandpaper-fragment-with-child")
lesson_fragment <- function(name = "lesson-fragment") {
system.file(name, package = "pegboard")
allowed <- c("lesson-fragment", "rmd-lesson",
"sandpaper-fragment", "sandpaper-fragment-with-child")
name <- match.arg(name, allowed)
return(system.file(name, package = "pegboard"))
}
14 changes: 13 additions & 1 deletion R/use_sandpaper.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
#' Convert a Jekyll-based lesson to a sandpaper-based lesson
#'
#' @details A Jekyll episode is littered with kramdown inline attribute tags
#' and liquid-formatted links. Converting to sandpaper means the following:
#'
#' - links using liquid formatting (e.g. `[text]({{ site.path }}/01-episode/)`
#' are replaced with their relative counterparts (`[text](01-episode.md)`.
#' - include statements for `links.md` and `base_path.md` are removed
#' - image attributes have the kramdown `:` removed
#' - code blocks with a kramdown inline attribute tag are converted to
#' commonmark via the internal [liquid_to_commonmark()].
#' - Lesson template-specific code is removed from the setup chunk in R
#' Markdown files.
#'
#' @param body the xml body of an episode
#' @param rmd if `TRUE`, the chunks will be converted to RMarkdown chunks
#' @param yml a list derived from the `_config.yml` file that defines the site
Expand All @@ -9,7 +21,7 @@
#' @param known_paths a character vector with the known paths in the lesson.
#' This is used to determine the correct path to other files in the lesson.
#' @return the body
#' @noRd
#' @keywords internal
use_sandpaper <- function(body, rmd = TRUE, yml = list(), path = NULL, known_paths = NULL) {
if (inherits(body, "xml_missing")) {
warning("episode body missing", call. = FALSE)
Expand Down
3 changes: 2 additions & 1 deletion _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ articles:
- title: "Developer Guides"
navbar: ~
contents:
- articles/validation
- intro-xml
- intro-episode
- intro-lesson
- articles/validation
2 changes: 1 addition & 1 deletion inst/sandpaper-fragment/site/built/md5sum.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"index.md" "fe56dfea3c4269cceb361470e2ca1437" "site/built/index.md" "2022-05-25"
"episodes/intro.Rmd" "3deb3673b36c3fd801e23cb85d9cd195" "site/built/intro.md" "2022-05-25"
"instructors/a.md" "bbaa216f7ef3b17267c460a8bd4fda6b" "site/built/a.md" "2022-05-25"
"learners/setup.md" "0e4e4d7028efbf7d756a08217126884f" "site/built/setup.md" "2022-05-25"
"learners/setup.md" "975096ee5adaf14b68c2ef89c2cade78" "site/built/setup.md" "2023-11-17"
"profiles/b.md" "bbaa216f7ef3b17267c460a8bd4fda6b" "site/built/b.md" "2022-05-25"
13 changes: 10 additions & 3 deletions man/Episode.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions man/Lesson.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading