Episode: Class representing XML source of a Carpentries episode

EpisodeR Documentation

Class representing XML source of a Carpentries episode

Description

Wrapper around an xml document to manipulate and inspect Carpentries episodes

Details

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 and kramdown flavours of Markdown.

Read more about this class in vignette("intro-episode", package = "pegboard").

Super class

tinkr::yarn -> Episode

Public fields

children

[character] a vector of absolute paths to child files if they exist.

parents

[character] a vector of absolute paths to immediate parent files if they exist

build_parents

[character] a vector of absolute paths to the final parent files that will trigger this child file to build

Active bindings

show_problems

[list] a list of all the problems that occurred in parsing the episode

headings

[xml_nodeset] all headings in the document

links

[xml_nodeset] all links (not images) in the document

images

[xml_nodeset] all image sources in the document

tags

[xml_nodeset] all the kramdown tags from the episode

questions

[character] the questions from the episode

keypoints

[character] the keypoints from the episode

objectives

[character] the objectives from the episode

challenges

[xml_nodeset] all the challenges blocks from the episode

solutions

[xml_nodeset] all the solutions blocks from the episode

output

[xml_nodeset] all the output blocks from the episode

error

[xml_nodeset] all the error blocks from the episode

warning

[xml_nodeset] all the warning blocks from the episode

code

[xml_nodeset] all the code blocks from the episode

name

[character] the name of the source file without the path

lesson

[character] the path to the lesson where the episode is from

has_children

[logical] an indicator of the presence of child files (TRUE) or their absence (FALSE)

has_parents

[logical] an indicator of the presence of parent files (TRUE) or their absence (FALSE)

Methods

Public methods

Inherited methods

Method new()

Create a new Episode

Usage
Episode$new(
  path = NULL,
  process_tags = TRUE,
  fix_links = TRUE,
  fix_liquid = FALSE,
  parents = NULL,
  ...
)
Arguments
path

[character] path to a markdown episode file on disk

process_tags

[logical] if TRUE (default), kramdown tags will be processed into attributes of the parent nodes. If FALSE, these tags will be treated as text

fix_links

[logical] if TRUE (default), links pointing to liquid tags (e.g. {{ page.root }}) and included links (those supplied by a call to ⁠{\% import links.md \%}⁠) will be appropriately processed as valid links.

fix_liquid

[logical] defaults to FALSE, which means data is immediately passed to tinkr::yarn. If TRUE, all liquid variables in relative links have spaces removed to allow the commonmark parser to interpret them as links.

parents

[list] a list of Episode objects that represent the immediate parents of this child

...

arguments passed on to tinkr::yarn and tinkr::to_xml()

Returns

A new Episode object with extracted XML data

Examples
scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
scope$name
scope$lesson
scope$challenges

Method confirm_sandpaper()

enforce that the episode is a sandpaper episode withtout going through the conversion steps. The default Episodes from pegboard were assumed to be generated using Jekyll with kramdown syntax. This is a bit of a kludge to bypass the normal checks for kramdown syntax and just assume pandoc syntax

Usage
Episode$confirm_sandpaper()

Method get_blocks()

return all block_quote elements within the Episode

Usage
Episode$get_blocks(type = NULL, level = 1L)
Arguments
type

the type of block quote in the Jekyll syntax like ".challenge", ".discussion", or ".solution"

level

the level of the block within the document. Defaults to 1, which represents all of the block_quotes are not nested within any other block quotes. Increase the nubmer to increase the level of nesting.

Returns

[xml_nodeset] all the blocks from the episode with the given tag and level.

Examples
scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
# get all the challenges
scope$get_blocks(".challenge")
# get the solutions
scope$get_blocks(".solution", level = 2)
\dontrun{

  # download the source files for r-novice-gampinder into a Lesson object
  rng <- get_lesson("swcarpentry/r-novice-gapminder")
  dsp1 <- rng$episodes[["04-data-structures-part1.md"]]
  # There are 9 blocks in total
  dsp1$get_blocks()
  # One is a callout block
  dsp1$get_blocks(".callout")
  # One is a discussion block
  dsp1$get_blocks(".discussion")
  # Seven are Challenge blocks
  dsp1$get_blocks(".challenge")
  # There are eight solution blocks:
  dsp1$get_blocks(".solution", level = 2L)
}

Method get_images()

fetch the image sources and optionally process them for easier parsing. The default version of this function is equivalent to the active binding ⁠$images⁠.

Usage
Episode$get_images(process = FALSE)
Arguments
process

if TRUE, images will be processed via the internal function process_images(), which will add the alt attribute, if available and extract img nodes from HTML blocks.

Returns

an xml_nodelist

Examples
loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
loop$get_images()
loop$get_images(process = TRUE)

Method label_divs()

label all the div elements within the Episode to extract them with ⁠$get_divs()⁠

Usage
Episode$label_divs()

Method get_divs()

return all div elements within the Episode

Usage
Episode$get_divs(type = NULL, include = FALSE)
Arguments
type

the type of div tag (e.g. 'challenge' or 'solution')

include

⁠\[logical\]⁠ if TRUE, the div tags will be included in the output. Defaults to FALSE, which will only return the text between the div tags.


Method get_yaml()

Extract the yaml metadata from the episode

Usage
Episode$get_yaml()

Method use_dovetail()

Ammend or add a setup code block to use {dovetail}

This will convert your lesson to use the dovetail R package for processing specialized block quotes which will do two things:

  1. convert your lesson from md to Rmd

  2. add to your setup chunk the following code

    library('dovetail')
    source(dvt_opts())
    

If there is no setup chunk, one will be created. If there is a setup chunk, then the source and knitr_fig_path calls will be removed.

Usage
Episode$use_dovetail()

Method use_sandpaper()

Use the sandpaper package for processing

This will convert your lesson to use the {sandpaper} R package for processing the lesson instead of Jekyll (default). Doing this will have the following effects:

  1. code blocks that were marked with liquid tags (e.g. ⁠{: .language-r}⁠ are converted to standard code blocks or Rmarkdown chunks (with language information at the top of the code block)

  2. If rmarkdown is used and the lesson contains python code, library('reticulate') will be added to the setup chunk of the lesson.

Usage
Episode$use_sandpaper(rmd = FALSE, yml = list())
Arguments
rmd

if TRUE, lessons will be converted to RMarkdown documents

yml

the list derived from the yml file for the episode


Method remove_error()

Remove error blocks

Usage
Episode$remove_error()

Method remove_output()

Remove output blocks

Usage
Episode$remove_output()

Method move_objectives()

move the objectives yaml item to the body

Usage
Episode$move_objectives()

Method move_keypoints()

move the keypoints yaml item to the body

Usage
Episode$move_keypoints()

Method move_questions()

move the questions yaml item to the body

Usage
Episode$move_questions()

Method get_challenge_graph()

Create a graph of the top-level elements for the challenges.

Usage
Episode$get_challenge_graph(recurse = TRUE)
Arguments
recurse

if TRUE (default), the content of the solutions will be included in the graph; FALSE will keep the solutions as block_quote elements.

Returns

a data frame with four columns representing all the elements within the challenges in the Episode:

  • Block: The sequential number of the challenge block

  • from: the inward elements

  • to: the outward elements

  • pos: the position in the markdown document

Note that there are three special node names:

  • challenge: start or end of the challenge block

  • solution: start of the solution block

  • lesson: start of the lesson block

Examples
scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
scope$get_challenge_graph()

Method show()

show the markdown contents on the screen

Usage
Episode$show(n = TRUE)
Arguments
n

a subset of elements to show, default TRUE for all lines

Returns

a character vector with one line for each line of output

Examples
scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
scope$head()
scope$tail()
scope$show()

Method head()

show the first n lines of markdown contents on the screen

Usage
Episode$head(n = 6L)
Arguments
n

the number of lines to show from the top

Returns

a character vector with one line for each line of output


Method tail()

show the first n lines of markdown contents on the screen

Usage
Episode$tail(n = 6L)
Arguments
n

the number of lines to show from the top

Returns

a character vector with one line for each line of output


Method write()

write the episode to disk as markdown

Usage
Episode$write(path = NULL, format = "md", edit = FALSE)
Arguments
path

the path to write your file to. Defaults to an empty directory in your temporary folder

format

one of "md" (default) or "xml". This will create a file with the correct extension in the path

edit

if TRUE, the file will open in an editor. Defaults to FALSE.

Returns

the episode object

Examples
scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
scope$write()

Method handout()

Create a trimmed-down RMarkdown document that strips prose and contains only important code chunks and challenge blocks without solutions.

Usage
Episode$handout(path = NULL, solutions = FALSE)
Arguments
path

(handout) a path to an R Markdown file to write. If this is NULL, no file will be written and the lines of the output will be returned.

solutions

if TRUE, include solutions in the output. Defaults to FALSE, which removes the solution blocks.

Returns

a character vector if path = NULL, otherwise, it is called for the side effect of creating a file.

Examples
lsn <- Lesson$new(lesson_fragment("sandpaper-fragment"), jekyll = FALSE)
e <- lsn$episodes[[1]]
cat(e$handout())
cat(e$handout(solution = TRUE))

Method reset()

Re-read episode from disk

Usage
Episode$reset()
Returns

the episode object

Examples
scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
xml2::xml_text(scope$tags[1])
xml2::xml_set_text(scope$tags[1], "{: .code}")
xml2::xml_text(scope$tags[1])
scope$reset()
xml2::xml_text(scope$tags[1])

Method isolate_blocks()

Remove all elements except for those within block quotes that have a kramdown tag. Note that this is a destructive process.

Usage
Episode$isolate_blocks()
Returns

the Episode object, invisibly

Examples
scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
scope$body # a full document with block quotes and code blocks, etc
scope$isolate_blocks()$body # only one challenge block_quote

Method unblock()

convert challenge blocks to roxygen-like code blocks

Usage
Episode$unblock(token = "#'", force = FALSE)
Arguments
token

the token to use to indicate non-code, Defaults to "#'"

force

force the conversion even if the conversion has already taken place

Returns

the Episode object, invisibly

Examples
loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
loop$body # a full document with block quotes and code blocks, etc
loop$get_blocks() # all the blocks in the episode
loop$unblock()
loop$get_blocks() # no blocks
loop$code # now there are two blocks with challenge tags

Method summary()

Get a high-level summary of the elements in the episode

Usage
Episode$summary()
Returns

a data frame with counts of the following elements per page:

  • sections: level 2 headings

  • headings: all headings

  • callouts: all callouts

  • challenges: subset of callouts

  • solutions: subset of callouts

  • code: all code block elements (excluding inline code)

  • output: subset of code that is displayed as output

  • warnining: subset of code that is displayed as a warning

  • error: subset of code that is displayed as an error

  • images: all images in markdown or HTML

  • links: all links in markdown or HTML


Method validate_headings()

perform validation on headings in a document.

This will validate the following aspects of all headings:

  • first heading starts at level 2 (first_heading_is_second_level)

  • greater than level 1 (greater_than_first_level)

  • increse sequentially (e.g. no jumps from 2 to 4) (are_sequential)

  • have names (have_names)

  • unique in their own hierarchy (are_unique)

Usage
Episode$validate_headings(verbose = TRUE, warn = TRUE)
Arguments
verbose

if TRUE (default), a message for each rule broken will be issued to the stderr. if FALSE, this will be silent.

warn

if TRUE (default), a warning will be issued if there are any failures in the tests.

Returns

a data frame with a variable number of rows and the follwoing columns:

  • episode the filename of the episode

  • heading the text from a heading

  • level the heading level

  • pos the position of the heading in the document

  • node the XML node that represents the heading

  • (the next five columns are the tests listed above)

  • path the path to the file.

Each row in the data frame represents an individual heading across the Lesson. See validate_headings() for more details.

Examples
# Example: There are multiple headings called "Solution" that are not
# nested within a higher-level heading and will throw an error
loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
loop$validate_headings()

Method validate_divs()

perform validation on divs in a document.

This will validate the following aspects of divs. See validate_divs() for details.

  • divs are of a known type (is_known)

Usage
Episode$validate_divs(warn = TRUE)
Arguments
warn

if TRUE (default), a warning message will be if there are any divs determined to be invalid. Set to FALSE if you want the table for processing later.

Returns

a logical TRUE for valid divs and FALSE for invalid divs.

Examples
loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
loop$validate_divs()

Method validate_links()

perform validation on links and images in a document.

This will validate the following aspects of links. See validate_links() for details.

  • External links use HTTPS (enforce_https)

  • Internal links exist (internal_okay)

  • External links are reachable (all_reachable) (planned)

  • Images have alt text (img_alt_text)

  • Link text is descriptive (descriptive)

  • Link text is more than a single letter (link_length)

Usage
Episode$validate_links(warn = TRUE)
Arguments
warn

if TRUE (default), a warning message will be if there are any links determined to be invalid. Set to FALSE if you want the table for processing later.

Returns

a logical TRUE for valid links and FALSE for invalid links.

Examples
loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
loop$validate_links()

Method clone()

The objects of this class are cloneable with this method.

Usage
Episode$clone(deep = FALSE)
Arguments
deep

Whether to make a deep clone.

Note

The current XLST spec for tinkr does not support kramdown, which the Carpentries Episodes are styled with, thus some block tags will be destructively modified in the conversion.

Examples


## ------------------------------------------------
## Method `Episode$new`
## ------------------------------------------------

scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
scope$name
scope$lesson
scope$challenges

## ------------------------------------------------
## Method `Episode$get_blocks`
## ------------------------------------------------

scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
# get all the challenges
scope$get_blocks(".challenge")
# get the solutions
scope$get_blocks(".solution", level = 2)
## Not run: 

  # download the source files for r-novice-gampinder into a Lesson object
  rng <- get_lesson("swcarpentry/r-novice-gapminder")
  dsp1 <- rng$episodes[["04-data-structures-part1.md"]]
  # There are 9 blocks in total
  dsp1$get_blocks()
  # One is a callout block
  dsp1$get_blocks(".callout")
  # One is a discussion block
  dsp1$get_blocks(".discussion")
  # Seven are Challenge blocks
  dsp1$get_blocks(".challenge")
  # There are eight solution blocks:
  dsp1$get_blocks(".solution", level = 2L)

## End(Not run)

## ------------------------------------------------
## Method `Episode$get_images`
## ------------------------------------------------


loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
loop$get_images()
loop$get_images(process = TRUE)

## ------------------------------------------------
## Method `Episode$get_challenge_graph`
## ------------------------------------------------

scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
scope$get_challenge_graph()

## ------------------------------------------------
## Method `Episode$show`
## ------------------------------------------------

scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
scope$head()
scope$tail()
scope$show()

## ------------------------------------------------
## Method `Episode$write`
## ------------------------------------------------

scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
scope$write()

## ------------------------------------------------
## Method `Episode$handout`
## ------------------------------------------------

lsn <- Lesson$new(lesson_fragment("sandpaper-fragment"), jekyll = FALSE)
e <- lsn$episodes[[1]]
cat(e$handout())
cat(e$handout(solution = TRUE))

## ------------------------------------------------
## Method `Episode$reset`
## ------------------------------------------------

scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
xml2::xml_text(scope$tags[1])
xml2::xml_set_text(scope$tags[1], "{: .code}")
xml2::xml_text(scope$tags[1])
scope$reset()
xml2::xml_text(scope$tags[1])

## ------------------------------------------------
## Method `Episode$isolate_blocks`
## ------------------------------------------------

scope <- Episode$new(file.path(lesson_fragment(), "_episodes", "17-scope.md"))
scope$body # a full document with block quotes and code blocks, etc
scope$isolate_blocks()$body # only one challenge block_quote

## ------------------------------------------------
## Method `Episode$unblock`
## ------------------------------------------------

loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
loop$body # a full document with block quotes and code blocks, etc
loop$get_blocks() # all the blocks in the episode
loop$unblock()
loop$get_blocks() # no blocks
loop$code # now there are two blocks with challenge tags

## ------------------------------------------------
## Method `Episode$validate_headings`
## ------------------------------------------------

# Example: There are multiple headings called "Solution" that are not
# nested within a higher-level heading and will throw an error
loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
loop$validate_headings()

## ------------------------------------------------
## Method `Episode$validate_divs`
## ------------------------------------------------

loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
loop$validate_divs()

## ------------------------------------------------
## Method `Episode$validate_links`
## ------------------------------------------------

loop <- Episode$new(file.path(lesson_fragment(), "_episodes", "14-looping-data-sets.md"))
loop$validate_links()

carpentries/pegboard documentation built on Nov. 13, 2024, 8:53 a.m.