From 310ed8a681b7ac82854eb2960ec8e60b6f7b5d5f Mon Sep 17 00:00:00 2001 From: "Zhian N. Kamvar" Date: Mon, 26 Jul 2021 16:39:23 -0700 Subject: [PATCH 01/10] move warning function; rename utils-themes --- R/get_dropdown.R | 9 --------- R/{utils-themes.R => utils-cli.R} | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) rename R/{utils-themes.R => utils-cli.R} (56%) diff --git a/R/get_dropdown.R b/R/get_dropdown.R index 0047c09cf..cf27e3dff 100644 --- a/R/get_dropdown.R +++ b/R/get_dropdown.R @@ -44,12 +44,3 @@ get_profiles <- function(path = ".", trim = TRUE) { as.character(get_resource_list(path, trim, "profiles", warn = TRUE)) } -warn_schedule <- function() { - msg <- "No schedule set, using Rmd files in {.file episodes/} directory." - msg2 <- "To remove this message, define your schedule in {.file config.yaml}" - msg3 <- "or use {.code set_episodes()} to generate it." - thm <- cli::cli_div(theme = sandpaper_cli_theme()) - cli::cli_alert_info(msg) - cli::cli_alert(cli::style_dim(paste(msg2, msg3)), class = "alert-suggestion") - cli::cli_end(thm) -} diff --git a/R/utils-themes.R b/R/utils-cli.R similarity index 56% rename from R/utils-themes.R rename to R/utils-cli.R index 969e4d1fa..a52e57a81 100644 --- a/R/utils-themes.R +++ b/R/utils-cli.R @@ -20,3 +20,12 @@ sandpaper_cli_theme <- function() { NULL ) } +warn_schedule <- function() { + msg <- "No schedule set, using Rmd files in {.file episodes/} directory." + msg2 <- "To remove this message, define your schedule in {.file config.yaml}" + msg3 <- "or use {.code set_episodes()} to generate it." + thm <- cli::cli_div(theme = sandpaper_cli_theme()) + cli::cli_alert_info(msg) + cli::cli_alert(cli::style_dim(paste(msg2, msg3)), class = "alert-suggestion") + cli::cli_end(thm) +} From 55349d628d7475067e6df8249ab185b3592941c8 Mon Sep 17 00:00:00 2001 From: "Zhian N. Kamvar" Date: Mon, 26 Jul 2021 17:38:29 -0700 Subject: [PATCH 02/10] update tests; move markdown messenger --- R/build_markdown.R | 4 +-- R/check_pandoc.R | 3 +- R/get_syllabus.R | 2 +- R/utils-paths-source.R | 19 +++++++------ R/utils-paths.R | 8 ------ R/utils-yaml.R | 5 +++- tests/testthat/_snaps/check_pandoc.md | 40 +++++++++++++++++++++++++++ tests/testthat/test-check_pandoc.R | 15 +++++----- tests/testthat/test-utils.R | 10 ++++++- 9 files changed, 75 insertions(+), 31 deletions(-) diff --git a/R/build_markdown.R b/R/build_markdown.R index 2f8e50136..7a1f6cb94 100644 --- a/R/build_markdown.R +++ b/R/build_markdown.R @@ -22,7 +22,7 @@ build_markdown <- function(path = ".", rebuild = FALSE, quiet = FALSE) { outdir <- path_built(path) # Determine build status for the episodes ------------------------------------ - source_list <- get_resource_list(path) + source_list <- get_resource_list(path, warn = TRUE) sources <- unlist(source_list, use.names = FALSE) names(sources) <- get_slug(sources) @@ -92,8 +92,6 @@ build_markdown <- function(path = ".", rebuild = FALSE, quiet = FALSE) { invisible(db$build) } - - remove_rendered_html <- function(episodes) { htmls <- fs::path_ext_set(episodes, "html") exists <- fs::file_exists(htmls) diff --git a/R/check_pandoc.R b/R/check_pandoc.R index adb1663ac..6b74aa505 100644 --- a/R/check_pandoc.R +++ b/R/check_pandoc.R @@ -32,7 +32,8 @@ check_pandoc <- function(quiet = TRUE, pv = "2.11", rv = "1.4") { pan_msg <- "You do not have pandoc installed on your PATH" } if (Sys.getenv("RSTUDIO", "0") == "1") { - rs_ver <- rstudioapi::getVersion() + # catch error for tests + rs_ver <- tryCatch(rstudioapi::getVersion(), error = function(e) "0.99") if (rs_ver < rv) { install_msg <- paste( "Please update your version of RStudio Desktop to version", diff --git a/R/get_syllabus.R b/R/get_syllabus.R index fa7b563d4..7b379cc8f 100644 --- a/R/get_syllabus.R +++ b/R/get_syllabus.R @@ -23,7 +23,7 @@ get_syllabus <- function(path = ".", questions = FALSE, use_built = TRUE) { # The syllabus is a table containing timings, links, and questions associated # with each episode. - sched <- get_episodes(path) + sched <- get_resource_list(path, trim = TRUE, subfolder = "episodes") lesson <- pegboard::Lesson$new(path, jekyll = FALSE, fix_links = FALSE) episodes <- lesson$episodes[sched] diff --git a/R/utils-paths-source.R b/R/utils-paths-source.R index d699a34ad..ec7968ad9 100644 --- a/R/utils-paths-source.R +++ b/R/utils-paths-source.R @@ -1,5 +1,5 @@ # All of these helper functions target the source of the lesson... that is, all -# of the files that git tracks. +# of the files that git tracks. path_config <- function(path) { home <- root_path(path) fs::path(home, "config.yaml") @@ -31,7 +31,7 @@ path_profiles <- function(inpath) { #' @param trim if `TRUE`, trim the paths to be relative to the lesson directory. #' Defaults to `FALSE`, which will return the absolute paths #' @param subfolder the subfolder to check. If this is `NULL`, all folders will -#' checked and returned (default), otherwise, this should be a string +#' checked and returned (default), otherwise, this should be a string #' specifying the folder name in the lesson (e.g. "episodes"). #' @param warn if `TRUE` and `subfolder = "episodes"`, a message is issued to #' the user if the episodes field of the configuration file is empty. @@ -44,6 +44,7 @@ get_resource_list <- function(path, trim = FALSE, subfolder = NULL, warn = FALSE root_path <- root recurse <- 1L cfg <- get_config(root) + should_warn <- warn && is.null(cfg[["episodes"]]) use_subfolder <- !is.null(subfolder) && length(subfolder) == 1L && is.character(subfolder) @@ -70,13 +71,13 @@ get_resource_list <- function(path, trim = FALSE, subfolder = NULL, warn = FALSE ), call. = FALSE) } - should_warn <- warn && is_episodes && is.null(cfg[["episodes"]]) - if (should_warn) warn_schedule() + should_warn <- should_warn && is_episodes recurse <- FALSE root_path <- fs::path(root, subfolder) } + if (should_warn) warn_schedule() res <- fs::dir_ls( root_path, @@ -100,14 +101,14 @@ get_resource_list <- function(path, trim = FALSE, subfolder = NULL, warn = FALSE } else { res <- split(res, fs::path_rel(fs::path_dir(res), root)) } - + if (use_subfolder) { names(res) <- subfolder } else { subfolder <- c("episodes", "learners", "instructors", "profiles") } - # These are the only four items that we need to consider order for. + # These are the only four items that we need to consider order for. for (i in subfolder) { config_order <- cfg[[i]] # If the configuration is not missing, then we have to rearrange the order. @@ -128,9 +129,9 @@ get_sources <- function(path, subfolder = "episodes") { get_artifacts <- function(path, subfolder = "episodes") { pe <- enforce_dir(fs::path(root_path(path), subfolder)) - fs::dir_ls(pe, regexp = "*R?md", - invert = TRUE, - type = "file", + fs::dir_ls(pe, regexp = "*R?md", + invert = TRUE, + type = "file", all = TRUE ) } diff --git a/R/utils-paths.R b/R/utils-paths.R index 845f0005d..c2cd227be 100644 --- a/R/utils-paths.R +++ b/R/utils-paths.R @@ -50,11 +50,3 @@ get_markdown_files <- function(path = NULL) { ) } -get_built_buddy <- function(path) { - pat <- fs::path_ext_set(get_slug(path), "md") - # Returns nothing if the pattern cannot be found - fs::dir_ls(path_built(path), regexp = pat, fixed = TRUE) -} - - - diff --git a/R/utils-yaml.R b/R/utils-yaml.R index 2d4678fde..f660cad03 100644 --- a/R/utils-yaml.R +++ b/R/utils-yaml.R @@ -4,7 +4,10 @@ politely_get_yaml <- function(path) { header <- readLines(path, n = 10, encoding = "UTF-8") barriers <- grep("^---$", header) if (length(barriers) == 0) { - stop("No yaml header") + thm <- cli::cli_div(theme = sandpaper_cli_theme()) + cli::cli_alert_danger("No yaml header found in the first 10 lines of {path}") + cli::cli_end(thm) + return(character(0)) } if (length(barriers) == 1) { to_skip <- 10L diff --git a/tests/testthat/_snaps/check_pandoc.md b/tests/testthat/_snaps/check_pandoc.md index 0fec5cec5..0f729b55b 100644 --- a/tests/testthat/_snaps/check_pandoc.md +++ b/tests/testthat/_snaps/check_pandoc.md @@ -34,3 +34,43 @@ ! You have pandoc version [version masked for testing] in '[path masked for testing]' → Please visit  to install the latest version. +# check_pandoc throws a message about installation for RStudio [plain] + + Code + expect_error(check_pandoc(pv = "42", rv = "94"), "Incorrect pandoc version", + fixed = TRUE) + Message + sandpaper requires pandoc version 42 or higher. + ! You have pandoc version [version masked for testing] in '[path masked for testing]' + > Please update your version of RStudio Desktop to version 94 or higher: + +# check_pandoc throws a message about installation for RStudio [ansi] + + Code + expect_error(check_pandoc(pv = "42", rv = "94"), "Incorrect pandoc version", + fixed = TRUE) + Message +  sandpaper requires pandoc version 42 or higher. + ! You have pandoc version [version masked for testing] in '[path masked for testing]' + > Please update your version of RStudio Desktop to version 94 or higher:  + +# check_pandoc throws a message about installation for RStudio [unicode] + + Code + expect_error(check_pandoc(pv = "42", rv = "94"), "Incorrect pandoc version", + fixed = TRUE) + Message + sandpaper requires pandoc version 42 or higher. + ! You have pandoc version [version masked for testing] in '[path masked for testing]' + → Please update your version of RStudio Desktop to version 94 or higher: + +# check_pandoc throws a message about installation for RStudio [fancy] + + Code + expect_error(check_pandoc(pv = "42", rv = "94"), "Incorrect pandoc version", + fixed = TRUE) + Message +  sandpaper requires pandoc version 42 or higher. + ! You have pandoc version [version masked for testing] in '[path masked for testing]' + → Please update your version of RStudio Desktop to version 94 or higher:  + diff --git a/tests/testthat/test-check_pandoc.R b/tests/testthat/test-check_pandoc.R index 474dabee4..971a6df20 100644 --- a/tests/testthat/test-check_pandoc.R +++ b/tests/testthat/test-check_pandoc.R @@ -12,14 +12,15 @@ cli::test_that_cli("check_pandoc() throws a message about installation", { expect_snapshot(expect_error(check_pandoc(pv = "42"), "Incorrect pandoc version")) }) -test_that("check_pandoc throws a message about installation for RStudio", { - skip_if_not(rstudioapi::isAvailable()) +cli::test_that_cli("check_pandoc throws a message about installation for RStudio", { skip_if_not(rmarkdown::pandoc_available()) - expect_error(check_pandoc(pv = "42", rv = "94"), - "{sandpaper} requires pandoc version 42 or higher", fixed = TRUE) - - expect_error(check_pandoc(pv = "42", rv = "94"), - "Please update your version of RStudio Desktop to version 94 or higher", fixed = TRUE) + withr::with_envvar(c(RSTUDIO = "1"), { + expect_snapshot({ + expect_error(check_pandoc(pv = "42", rv = "94"), + "Incorrect pandoc version", + fixed = TRUE) + }) +}) }) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index f3f2cf80e..db883faae 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -4,6 +4,14 @@ test_that("null pipe works", { expect_equal(NA_character_ %||% LETTERS, NA_character_) }) +cli::test_that_cli("polite yaml throws a message when there is no yaml", { + + withr::local_file(tmp <- tempfile()) + cat("# A header\n\nbut no yaml :/\n", file = tmp) + expect_message(politely_get_yaml(tmp), "No yaml header found in the first 10 lines") + +}) + test_that("polite yaml works", { @@ -37,7 +45,7 @@ b: is it? This is not poetry " - tmp <- tempfile() + withr::local_file(tmp <- tempfile()) cat(yaml, file = tmp, sep = "\n") rl <- readLines(tmp) pgy <- politely_get_yaml(tmp) From 4fc3dedff8a21d16b1763fe50e8c362ead5d4648 Mon Sep 17 00:00:00 2001 From: "Zhian N. Kamvar" Date: Mon, 26 Jul 2021 18:00:23 -0700 Subject: [PATCH 03/10] split off function to find existing files --- R/utils-paths-source.R | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/R/utils-paths-source.R b/R/utils-paths-source.R index ec7968ad9..a68b8daef 100644 --- a/R/utils-paths-source.R +++ b/R/utils-paths-source.R @@ -110,14 +110,8 @@ get_resource_list <- function(path, trim = FALSE, subfolder = NULL, warn = FALSE # These are the only four items that we need to consider order for. for (i in subfolder) { - config_order <- cfg[[i]] # If the configuration is not missing, then we have to rearrange the order. - if (!is.null(config_order)) { - # Confirm that the order exists - paths <- res[[i]] - default_order <- fs::path_file(paths) - res[[i]] <- paths[match(config_order, default_order, nomatch = 0)] - } + res[[i]] <- parse_file_matches(res[[i]], cfg[[i]]) } if (use_subfolder) res[[subfolder]] else res[names(res) != "site"] } @@ -136,3 +130,11 @@ get_artifacts <- function(path, subfolder = "episodes") { ) } +parse_file_matches <- function(reality, hopes = NULL, warn = FALSE) { + if (is.null(hopes)) { + return(reality) + } + should_warn <- warn && getOption("sandpaper.show_draft", TRUE) + # Confirm that the order exists + reality[match(hopes, fs::path_file(reality), nomatch = 0)] +} From 9daf654a0503358fe3f14b20f209198187cd6546 Mon Sep 17 00:00:00 2001 From: "Zhian N. Kamvar" Date: Mon, 26 Jul 2021 18:47:58 -0700 Subject: [PATCH 04/10] update CLI [skip ci] I still need to update the tests --- R/utils-cli.R | 36 +++++++++++++++++++++++++++++ R/utils-paths-source.R | 38 +++++++++++++++++++++++++++---- R/utils-yaml.R | 13 ----------- tests/testthat/test-get_episode.R | 11 ++------- 4 files changed, 72 insertions(+), 26 deletions(-) diff --git a/R/utils-cli.R b/R/utils-cli.R index a52e57a81..fe203b88f 100644 --- a/R/utils-cli.R +++ b/R/utils-cli.R @@ -5,6 +5,9 @@ remove_cli_decoration <- function(msg) { sandpaper_cli_theme <- function() { list( + ul = list( + "list-style-type" = function() "-" + ), ".alert-warning" = list( before = function() paste0(cli::col_yellow(cli::symbol$cirle), " ") ), @@ -20,6 +23,7 @@ sandpaper_cli_theme <- function() { NULL ) } + warn_schedule <- function() { msg <- "No schedule set, using Rmd files in {.file episodes/} directory." msg2 <- "To remove this message, define your schedule in {.file config.yaml}" @@ -29,3 +33,35 @@ warn_schedule <- function() { cli::cli_alert(cli::style_dim(paste(msg2, msg3)), class = "alert-suggestion") cli::cli_end(thm) } + +show_changed_yaml <- function(sched, order, yaml, what = "episodes") { + + # display for the user to distinguish what was added and what was taken + removed <- sched %nin% order + added <- order %nin% sched + thm <- cli::cli_div(theme = sandpaper_cli_theme()) + on.exit(cli::cli_end(thm)) + pid <- cli::cli_par() + cli::cli_text("{what}:") + lid <- cli::cli_ul() + for (i in seq(order)) { + if (added[i]) { + thing <- cli::style_bold(cli::col_cyan(order[i])) + } else { + thing <- order[i] + } + cli::cli_li("{thing}") + } + cli::cli_end(lid) + cli::cli_end(pid) + if (any(removed)) { + cli::cli_rule("Removed {what}") + lid <- cli::cli_ul() + the_removed <- sched[removed] + for (i in the_removed) { + cli::cli_li("{cli::style_italic(i)}") + } + cli::cli_end(lid) + + } +} diff --git a/R/utils-paths-source.R b/R/utils-paths-source.R index a68b8daef..87be93b5f 100644 --- a/R/utils-paths-source.R +++ b/R/utils-paths-source.R @@ -111,7 +111,7 @@ get_resource_list <- function(path, trim = FALSE, subfolder = NULL, warn = FALSE # These are the only four items that we need to consider order for. for (i in subfolder) { # If the configuration is not missing, then we have to rearrange the order. - res[[i]] <- parse_file_matches(res[[i]], cfg[[i]]) + res[[i]] <- parse_file_matches(res[[i]], cfg[[i]], warn = warn, subfolder) } if (use_subfolder) res[[subfolder]] else res[names(res) != "site"] } @@ -130,11 +130,41 @@ get_artifacts <- function(path, subfolder = "episodes") { ) } -parse_file_matches <- function(reality, hopes = NULL, warn = FALSE) { +parse_file_matches <- function(reality, hopes = NULL, warn = FALSE, subfolder) { if (is.null(hopes)) { return(reality) } - should_warn <- warn && getOption("sandpaper.show_draft", TRUE) + real_files <- fs::path_file(reality) # Confirm that the order exists - reality[match(hopes, fs::path_file(reality), nomatch = 0)] + matches <- match(hopes, real_files, nomatch = 0) + + if (warn) { + thm <- cli::cli_div(theme = sandpaper_cli_theme()) + on.exit(cli::cli_end(thm), add = TRUE) + } + + warn_missing_config <- warn && any(matches == 0) + if (warn_missing_config) { + broken_dreams <- hopes %nin% real_files + cli::cli_alert_warning(c( + "The following files were specified in {.file config.yaml}, but do not exist:" + )) + cli::cli_text("{subfolder}:") + lid <- cli::cli_ul() + lapply(hopes[broken_dreams], + function(i) cli::cli_li("{.file {i}}") + ) + cli::cli_end(lid) + } + + if (warn && getOption("sandpaper.show_draft", FALSE)) { + dreams <- reality[real_files %nin% hopes] + if (length(dreams)) { + cli::cli_alert_info( + "The following files are still in draft: {.file {dreams}}" + ) + } + } + + reality[matches] } diff --git a/R/utils-yaml.R b/R/utils-yaml.R index f660cad03..5b7a07fb5 100644 --- a/R/utils-yaml.R +++ b/R/utils-yaml.R @@ -46,19 +46,6 @@ write_pkgdown_yaml <- function(yaml, path) { yaml_writer(yaml, path_site_yaml(path)) } -show_changed_yaml <- function(sched, order, yaml, what = "episodes") { - - # display for the user to distinguish what was added and what was taken - removed <- sched %nin% order - added <- order %nin% sched - order[added] <- cli::style_bold(cli::col_green(order[added])) - cli::cat_line(paste0(what, ":")) - cli::cat_bullet(order, bullet = "line") - if (any(removed)) { - cli::cli_rule(paste("Removed", what)) - cli::cat_bullet(sched[removed], bullet = "cross", bullet_col = "red") - } -} #' Create a valid, opinionated yaml list for insertion into a whisker template #' diff --git a/tests/testthat/test-get_episode.R b/tests/testthat/test-get_episode.R index 0565ed9a4..da27dfee1 100644 --- a/tests/testthat/test-get_episode.R +++ b/tests/testthat/test-get_episode.R @@ -1,17 +1,11 @@ { -# tmpdir <- fs::file_temp() -# fs::dir_create(tmpdir) -# tmp <- fs::path(tmpdir, "lesson-example") -# withr::defer(fs::dir_delete(tmp)) -# res <- create_lesson(tmp, open = FALSE) -# suppressMessages(e <- get_episodes(res)) -# set_episodes(res, e, write = TRUE) tmp <- res <- restore_fixture() +suppressMessages(e <- get_episodes(res)) +set_episodes(res, e, write = TRUE) } test_that("set_episode() will throw a warning if an episode does not exist", { - skip("currently writing") bad <- c(e, "I-do-not-exist.md") expect_message(set_episodes(res, bad, write = TRUE)) @@ -25,7 +19,6 @@ test_that("set_episode() will throw a warning if an episode does not exist", { test_that("get_episode() will throw a warning if an episode in config does not exist", { # Create a new episode that does not exist - skip("currently writing") cfg <- readLines(fs::path(res, "config.yaml"), encoding = "UTF-8") episode_line <- grep("^episodes", cfg) From caec528a1a566aa44c14ab29764b9f63b37e37c6 Mon Sep 17 00:00:00 2001 From: "Zhian N. Kamvar" Date: Tue, 27 Jul 2021 10:35:20 -0700 Subject: [PATCH 05/10] update draft reporting; add error for phantom file --- R/set_dropdown.R | 9 +++ R/utils-cli.R | 40 +++++++++-- R/utils-paths-source.R | 30 ++------ tests/testthat/_snaps/get_episode.md | 100 ++++++++++++++++++++++++++ tests/testthat/_snaps/set_dropdown.md | 62 +++++++++++++++- tests/testthat/test-get_episode.R | 24 +++++-- tests/testthat/test-set_dropdown.R | 2 +- 7 files changed, 229 insertions(+), 38 deletions(-) create mode 100644 tests/testthat/_snaps/get_episode.md diff --git a/R/set_dropdown.R b/R/set_dropdown.R index ba35e23cd..a7b35f2b2 100644 --- a/R/set_dropdown.R +++ b/R/set_dropdown.R @@ -26,6 +26,14 @@ #' set_dropdown <- function(path = ".", order = NULL, write = FALSE, folder) { check_order(order, folder) + real_files <- fs::path_file(fs::dir_ls( + fs::path(path, folder), + type = "file", + regexp = "[.]R?md" + )) + if (any(!order %in% real_files)) { + error_missing_config(order, real_files, folder) + } yaml <- get_config(path) sched <- yaml[[folder]] sched <- if (is.null(sched) && folder == "episodes") yaml[["schedule"]] else sched @@ -48,6 +56,7 @@ set_dropdown <- function(path = ".", order = NULL, write = FALSE, folder) { invisible() } + #' @export #' @rdname set_dropdown set_episodes <- function(path = ".", order = NULL, write = FALSE) { diff --git a/R/utils-cli.R b/R/utils-cli.R index fe203b88f..171f3ee2d 100644 --- a/R/utils-cli.R +++ b/R/utils-cli.R @@ -7,19 +7,19 @@ sandpaper_cli_theme <- function() { list( ul = list( "list-style-type" = function() "-" - ), + ), ".alert-warning" = list( before = function() paste0(cli::col_yellow(cli::symbol$cirle), " ") - ), + ), ".alert-danger" = list( before = function() paste0(cli::col_red("!"), " ") - ), + ), ".alert-success" = list( before = function() paste0(cli::col_cyan(cli::symbol$circle_filled), " ") - ), + ), ".alert-suggestion" = list( "font-style" = "italic" - ), + ), NULL ) } @@ -65,3 +65,33 @@ show_changed_yaml <- function(sched, order, yaml, what = "episodes") { } } +message_draft_files <- function(hopes, real_files, subfolder) { + thm <- cli::cli_div(theme = sandpaper_cli_theme()) + on.exit(cli::cli_end(thm), add = TRUE) + dreams <- fs::path(subfolder, real_files[real_files %nin% hopes]) + if (length(dreams)) { + cli::cli_alert_info( + "{.emph Files are in draft: {.file {dreams}}}" + ) + } +} + +error_missing_config <- function(hopes, reality, subfolder) { + thm <- cli::cli_div(theme = sandpaper_cli_theme()) + on.exit(cli::cli_end(thm), add = TRUE) + broken_dreams <- hopes %nin% reality + cli::cli_text("{subfolder}:") + lid <- cli::cli_ul() + for (i in seq(hopes)) { + if (broken_dreams[i]) { + cli::cli_li("{cli::symbol$cross} {.strong {hopes[i]}}") + } else { + cli::cli_li("{.file {hopes[i]}}") + } + } + cli::cli_end(lid) + cli::cli_abort(c( + "All files in {.file config.yaml} must exist", + "*" = "Files marked with {cli::symbol$cross} are not present" + )) +} diff --git a/R/utils-paths-source.R b/R/utils-paths-source.R index 87be93b5f..177b27151 100644 --- a/R/utils-paths-source.R +++ b/R/utils-paths-source.R @@ -138,32 +138,16 @@ parse_file_matches <- function(reality, hopes = NULL, warn = FALSE, subfolder) { # Confirm that the order exists matches <- match(hopes, real_files, nomatch = 0) - if (warn) { - thm <- cli::cli_div(theme = sandpaper_cli_theme()) - on.exit(cli::cli_end(thm), add = TRUE) - } + missing_config <- any(matches == 0) - warn_missing_config <- warn && any(matches == 0) - if (warn_missing_config) { - broken_dreams <- hopes %nin% real_files - cli::cli_alert_warning(c( - "The following files were specified in {.file config.yaml}, but do not exist:" - )) - cli::cli_text("{subfolder}:") - lid <- cli::cli_ul() - lapply(hopes[broken_dreams], - function(i) cli::cli_li("{.file {i}}") - ) - cli::cli_end(lid) + if (missing_config) { + error_missing_config(hopes, real_files, subfolder) } - if (warn && getOption("sandpaper.show_draft", FALSE)) { - dreams <- reality[real_files %nin% hopes] - if (length(dreams)) { - cli::cli_alert_info( - "The following files are still in draft: {.file {dreams}}" - ) - } + show_drafts <- warn && getOption("sandpaper.show_draft", FALSE) + + if (show_drafts) { + message_draft_files(hopes, real_files, subfolder) } reality[matches] diff --git a/tests/testthat/_snaps/get_episode.md b/tests/testthat/_snaps/get_episode.md new file mode 100644 index 000000000..0b6132314 --- /dev/null +++ b/tests/testthat/_snaps/get_episode.md @@ -0,0 +1,100 @@ +# set_episode() will throw an error if an episode does not exist [plain] + + Code + expect_error(set_episodes(res, bad, write = TRUE)) + Message + episodes: + - '01-introduction.Rmd' + - x I-do-not-exist.md + +# set_episode() will throw an error if an episode does not exist [ansi] + + Code + expect_error(set_episodes(res, bad, write = TRUE)) + Message + episodes: + - 01-introduction.Rmd + - x I-do-not-exist.md + +# set_episode() will throw an error if an episode does not exist [unicode] + + Code + expect_error(set_episodes(res, bad, write = TRUE)) + Message + episodes: + - '01-introduction.Rmd' + - ✖ I-do-not-exist.md + +# set_episode() will throw an error if an episode does not exist [fancy] + + Code + expect_error(set_episodes(res, bad, write = TRUE)) + Message + episodes: + - 01-introduction.Rmd + - ✖ I-do-not-exist.md + +# get_episode() will throw a message about episode in draft [plain] + + Code + drafty_out <- get_episodes(res) + Message + i Files are in draft: 'episodes/02-new.Rmd' + +# get_episode() will throw a message about episode in draft [ansi] + + Code + drafty_out <- get_episodes(res) + Message + i Files are in draft: episodes/02-new.Rmd + +# get_episode() will throw a message about episode in draft [unicode] + + Code + drafty_out <- get_episodes(res) + Message + ℹ Files are in draft: 'episodes/02-new.Rmd' + +# get_episode() will throw a message about episode in draft [fancy] + + Code + drafty_out <- get_episodes(res) + Message + ℹ Files are in draft: episodes/02-new.Rmd + +# get_episode() will throw a warning if an episode in config does not exist [plain] + + Code + expect_error(get_episodes(res)) + Message + episodes: + - '01-introduction.Rmd' + - x I-am-an-impostor.md + +# get_episode() will throw a warning if an episode in config does not exist [ansi] + + Code + expect_error(get_episodes(res)) + Message + episodes: + - 01-introduction.Rmd + - x I-am-an-impostor.md + +# get_episode() will throw a warning if an episode in config does not exist [unicode] + + Code + expect_error(get_episodes(res)) + Message + episodes: + - '01-introduction.Rmd' + - ✖ I-am-an-impostor.md + +# get_episode() will throw a warning if an episode in config does not exist [fancy] + + Code + expect_error(get_episodes(res)) + Message + episodes: + - 01-introduction.Rmd + - ✖ I-am-an-impostor.md + diff --git a/tests/testthat/_snaps/set_dropdown.md b/tests/testthat/_snaps/set_dropdown.md index 4ae71ea26..38688cff7 100644 --- a/tests/testthat/_snaps/set_dropdown.md +++ b/tests/testthat/_snaps/set_dropdown.md @@ -10,11 +10,67 @@ Code set_episodes(tmp, s[1]) - Output + Message episodes: - 01-introduction.Rmd + + -- Removed episodes ------------------------------------------------------------ + - 02-new.Rmd + +# set_episodes() will display the modifications if write is not specified [ansi] + + Code + s <- get_episodes(tmp) + Message + i No schedule set, using Rmd files in episodes/ directory. + > To remove this message, define your schedule in config.yaml or use `set_episodes()` to generate it. + +--- + + Code + set_episodes(tmp, s[1]) Message + episodes: + - 01-introduction.Rmd + -- Removed episodes ------------------------------------------------------------ - Output - x 02-new.Rmd + - 02-new.Rmd + +# set_episodes() will display the modifications if write is not specified [unicode] + + Code + s <- get_episodes(tmp) + Message + ℹ No schedule set, using Rmd files in 'episodes/' directory. + → To remove this message, define your schedule in 'config.yaml' or use `set_episodes()` to generate it. + +--- + + Code + set_episodes(tmp, s[1]) + Message + episodes: + - 01-introduction.Rmd + + ── Removed episodes ──────────────────────────────────────────────────────────── + - 02-new.Rmd + +# set_episodes() will display the modifications if write is not specified [fancy] + + Code + s <- get_episodes(tmp) + Message + ℹ No schedule set, using Rmd files in episodes/ directory. + → To remove this message, define your schedule in config.yaml or use `set_episodes()` to generate it. + +--- + + Code + set_episodes(tmp, s[1]) + Message + episodes: + - 01-introduction.Rmd + + ── Removed episodes ──────────────────────────────────────────────────────────── + - 02-new.Rmd diff --git a/tests/testthat/test-get_episode.R b/tests/testthat/test-get_episode.R index da27dfee1..3d647ed97 100644 --- a/tests/testthat/test-get_episode.R +++ b/tests/testthat/test-get_episode.R @@ -4,19 +4,31 @@ suppressMessages(e <- get_episodes(res)) set_episodes(res, e, write = TRUE) } -test_that("set_episode() will throw a warning if an episode does not exist", { +cli::test_that_cli("set_episode() will throw an error if an episode does not exist", { bad <- c(e, "I-do-not-exist.md") - expect_message(set_episodes(res, bad, write = TRUE)) + expect_snapshot(expect_error(set_episodes(res, bad, write = TRUE))) expect_silent(bad_out <- get_episodes(res)) + # The output equals the only episode in there expect_equal(bad_out, e) }) -test_that("get_episode() will throw a warning if an episode in config does not exist", { +cli::test_that_cli("get_episode() will throw a message about episode in draft", { + + withr::local_options(list("sandpaper.show_draft" = TRUE)) + if (!fs::file_exists(fs::path(res, "episodes", "02-new.Rmd"))) { + create_episode("new", add = FALSE, path = res) + } + expect_snapshot(drafty_out <- get_episodes(res)) + expect_equal(drafty_out, e) + +}) + +cli::test_that_cli("get_episode() will throw a warning if an episode in config does not exist", { # Create a new episode that does not exist cfg <- readLines(fs::path(res, "config.yaml"), encoding = "UTF-8") @@ -28,10 +40,10 @@ test_that("get_episode() will throw a warning if an episode in config does not e cfg[seq(episode_line + 2L, length(cfg))] ) - writeLines(new_cfg, fs::path(res, "config.yaml")) + withr::defer(writeLines(cfg, fs::path(res, "config.yaml"))) - expect_message(bad_out <- get_episodes(res)) + writeLines(new_cfg, fs::path(res, "config.yaml")) - expect_equal(bad_out, e) + expect_snapshot(expect_error(get_episodes(res))) }) diff --git a/tests/testthat/test-set_dropdown.R b/tests/testthat/test-set_dropdown.R index a8f376280..79cef3f44 100644 --- a/tests/testthat/test-set_dropdown.R +++ b/tests/testthat/test-set_dropdown.R @@ -50,7 +50,7 @@ cli::test_that_cli("set_episodes() will display the modifications if write is no set_episodes(tmp, s[1], write = TRUE) expect_equal(get_episodes(tmp), s[1], ignore_attr = TRUE) -}, configs = "plain") +}) test_that("set_episodes() will error if no proposal is defined", { From 85ff401441c13009293e8f54780fd423474cefe1 Mon Sep 17 00:00:00 2001 From: "Zhian N. Kamvar" Date: Tue, 27 Jul 2021 10:56:19 -0700 Subject: [PATCH 06/10] fix tests --- tests/testthat/_snaps/set_dropdown.md | 57 --------------------------- tests/testthat/test-set_dropdown.R | 2 +- 2 files changed, 1 insertion(+), 58 deletions(-) diff --git a/tests/testthat/_snaps/set_dropdown.md b/tests/testthat/_snaps/set_dropdown.md index 38688cff7..a7edc29a8 100644 --- a/tests/testthat/_snaps/set_dropdown.md +++ b/tests/testthat/_snaps/set_dropdown.md @@ -17,60 +17,3 @@ -- Removed episodes ------------------------------------------------------------ - 02-new.Rmd -# set_episodes() will display the modifications if write is not specified [ansi] - - Code - s <- get_episodes(tmp) - Message - i No schedule set, using Rmd files in episodes/ directory. - > To remove this message, define your schedule in config.yaml or use `set_episodes()` to generate it. - ---- - - Code - set_episodes(tmp, s[1]) - Message - episodes: - - 01-introduction.Rmd - - -- Removed episodes ------------------------------------------------------------ - - 02-new.Rmd - -# set_episodes() will display the modifications if write is not specified [unicode] - - Code - s <- get_episodes(tmp) - Message - ℹ No schedule set, using Rmd files in 'episodes/' directory. - → To remove this message, define your schedule in 'config.yaml' or use `set_episodes()` to generate it. - ---- - - Code - set_episodes(tmp, s[1]) - Message - episodes: - - 01-introduction.Rmd - - ── Removed episodes ──────────────────────────────────────────────────────────── - - 02-new.Rmd - -# set_episodes() will display the modifications if write is not specified [fancy] - - Code - s <- get_episodes(tmp) - Message - ℹ No schedule set, using Rmd files in episodes/ directory. - → To remove this message, define your schedule in config.yaml or use `set_episodes()` to generate it. - ---- - - Code - set_episodes(tmp, s[1]) - Message - episodes: - - 01-introduction.Rmd - - ── Removed episodes ──────────────────────────────────────────────────────────── - - 02-new.Rmd - diff --git a/tests/testthat/test-set_dropdown.R b/tests/testthat/test-set_dropdown.R index 79cef3f44..1fdd81298 100644 --- a/tests/testthat/test-set_dropdown.R +++ b/tests/testthat/test-set_dropdown.R @@ -50,7 +50,7 @@ cli::test_that_cli("set_episodes() will display the modifications if write is no set_episodes(tmp, s[1], write = TRUE) expect_equal(get_episodes(tmp), s[1], ignore_attr = TRUE) -}) +}, "plain") test_that("set_episodes() will error if no proposal is defined", { From 76db415d6d97dc41c6f21147065440860fb878c6 Mon Sep 17 00:00:00 2001 From: "Zhian N. Kamvar" Date: Tue, 27 Jul 2021 11:30:00 -0700 Subject: [PATCH 07/10] add get_drafts(); update NEWS --- NEWS.md | 15 ++++++ R/get_drafts.R | 39 ++++++++++++++ R/utils-cli.R | 11 ++++ man/get_drafts.Rd | 35 ++++++++++++ tests/testthat/_snaps/get_drafts.md | 84 +++++++++++++++++++++++++++++ tests/testthat/test-get_drafts.R | 32 +++++++++++ 6 files changed, 216 insertions(+) create mode 100644 R/get_drafts.R create mode 100644 man/get_drafts.Rd create mode 100644 tests/testthat/_snaps/get_drafts.md create mode 100644 tests/testthat/test-get_drafts.R diff --git a/NEWS.md b/NEWS.md index 18644ad60..691f6f5d0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,18 @@ +# sandpaper 0.0.0.9033 + +NEW FEATURES +------------ + + - `get_drafts()` will report any markdown files that are not currently + published in the lesson. + - Draft alert notifications are controlled by the `"sandpaper.show_draft"` + option. To turn off these messages, use + `options(sandpaper.show_draft = FALSE)` + - The `set_dropdown()` family of functions will now throw an error if an + author attempts to add a file that does not exist + - An error will occurr if the files listed in `config.yaml` do not exist in the + lesson with an informative message highlighting the files that are missing. + # sandpaper 0.0.0.9032 MISC diff --git a/R/get_drafts.R b/R/get_drafts.R new file mode 100644 index 000000000..8f4c9ceb2 --- /dev/null +++ b/R/get_drafts.R @@ -0,0 +1,39 @@ +#' Show files in draft form +#' +#' By default, {sandpaper} will use the files in alphabetical order as they are +#' presented in the folders, however, it is **strongly** for authors to specify +#' the order of the files in their lessons, so that it's easy to rearrange or +#' add, split, or rearrange files. +#' +#' This mechanism also allows authors to work on files in a draft form without +#' them being published. This function will list and show the files in draft for +#' automation and audit. +#' +#' @param path path to the the sandpaper lesson +#' @param folder the specific folder for which to list the draft files. Defaults +#' to `NULL`, which indicates all folders listed in `config.yaml`. +#' @param message if `TRUE` (default), an informative message about the files +#' that are in draft status are printed to the screen. +#' +#' @return a vector of paths to files in draft and a message (if specified) +get_drafts <- function(path, folder = NULL, message = getOption("sandpaper.show_draft", TRUE)) { + cfg <- get_config(path) + if (is.null(folder)) { + folder <- c("episodes", "learners", "instructors", "profiles") + } + res <- character(0) + for (f in folder) { + if (is.null(cfg[[f]])) { + if (message) message_default_draft(f) + next + } + drafts <- get_sources(path, f) + if (any(in_draft <- fs::path_file(drafts) %nin% cfg[[f]])) { + if (message) message_draft_files(cfg[[f]], fs::path_file(drafts), f) + res <- c(res, drafts[in_draft]) + } else { + if (message) message_no_draft(f) + } + } + fs::path(res) +} diff --git a/R/utils-cli.R b/R/utils-cli.R index 171f3ee2d..ad830a0a5 100644 --- a/R/utils-cli.R +++ b/R/utils-cli.R @@ -65,6 +65,17 @@ show_changed_yaml <- function(sched, order, yaml, what = "episodes") { } } + +message_default_draft <- function(subfolder) { + message_no_draft(subfolder, " (config.yaml empty)") +} + +message_no_draft <- function(subfolder, append = "") { + thm <- cli::cli_div(theme = sandpaper_cli_theme()) + on.exit(cli::cli_end(thm), add = TRUE) + cli::cli_alert_info("All files in {.file {subfolder}/} published{append}") +} + message_draft_files <- function(hopes, real_files, subfolder) { thm <- cli::cli_div(theme = sandpaper_cli_theme()) on.exit(cli::cli_end(thm), add = TRUE) diff --git a/man/get_drafts.Rd b/man/get_drafts.Rd new file mode 100644 index 000000000..9b3887e74 --- /dev/null +++ b/man/get_drafts.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_drafts.R +\name{get_drafts} +\alias{get_drafts} +\title{Show files in draft form} +\usage{ +get_drafts( + path, + folder = NULL, + message = getOption("sandpaper.show_draft", TRUE) +) +} +\arguments{ +\item{path}{path to the the sandpaper lesson} + +\item{folder}{the specific folder for which to list the draft files. Defaults +to \code{NULL}, which indicates all folders listed in \code{config.yaml}.} + +\item{message}{if \code{TRUE} (default), an informative message about the files +that are in draft status are printed to the screen.} +} +\value{ +a vector of paths to files in draft and a message (if specified) +} +\description{ +By default, {sandpaper} will use the files in alphabetical order as they are +presented in the folders, however, it is \strong{strongly} for authors to specify +the order of the files in their lessons, so that it's easy to rearrange or +add, split, or rearrange files. +} +\details{ +This mechanism also allows authors to work on files in a draft form without +them being published. This function will list and show the files in draft for +automation and audit. +} diff --git a/tests/testthat/_snaps/get_drafts.md b/tests/testthat/_snaps/get_drafts.md new file mode 100644 index 000000000..3c0c68a71 --- /dev/null +++ b/tests/testthat/_snaps/get_drafts.md @@ -0,0 +1,84 @@ +# Default state reports all episodes published [plain] + + Code + drf <- get_drafts(res, "episodes") + Message + i All files in 'episodes/' published (config.yaml empty) + +# Default state reports all episodes published [ansi] + + Code + drf <- get_drafts(res, "episodes") + Message + i All files in episodes/ published (config.yaml empty) + +# Default state reports all episodes published [unicode] + + Code + drf <- get_drafts(res, "episodes") + Message + ℹ All files in 'episodes/' published (config.yaml empty) + +# Default state reports all episodes published [fancy] + + Code + drf <- get_drafts(res, "episodes") + Message + ℹ All files in episodes/ published (config.yaml empty) + +# Draft episodes are and added episodes ignored [plain] + + Code + drf <- get_drafts(res, "episodes") + Message + i Files are in draft: 'episodes/02-new.Rmd' + +# Draft episodes are and added episodes ignored [ansi] + + Code + drf <- get_drafts(res, "episodes") + Message + i Files are in draft: episodes/02-new.Rmd + +# Draft episodes are and added episodes ignored [unicode] + + Code + drf <- get_drafts(res, "episodes") + Message + ℹ Files are in draft: 'episodes/02-new.Rmd' + +# Draft episodes are and added episodes ignored [fancy] + + Code + drf <- get_drafts(res, "episodes") + Message + ℹ Files are in draft: episodes/02-new.Rmd + +# No draft episodes reports all episodes published [plain] + + Code + drf <- get_drafts(res, "episodes") + Message + i All files in 'episodes/' published + +# No draft episodes reports all episodes published [ansi] + + Code + drf <- get_drafts(res, "episodes") + Message + i All files in episodes/ published + +# No draft episodes reports all episodes published [unicode] + + Code + drf <- get_drafts(res, "episodes") + Message + ℹ All files in 'episodes/' published + +# No draft episodes reports all episodes published [fancy] + + Code + drf <- get_drafts(res, "episodes") + Message + ℹ All files in episodes/ published + diff --git a/tests/testthat/test-get_drafts.R b/tests/testthat/test-get_drafts.R new file mode 100644 index 000000000..e2835c5e1 --- /dev/null +++ b/tests/testthat/test-get_drafts.R @@ -0,0 +1,32 @@ +{ + res <- restore_fixture() + create_episode("new", add = FALSE, path = res) +} + +cli::test_that_cli("Default state reports all episodes published", { + + expect_snapshot(drf <- get_drafts(res, "episodes")) + expect_length(drf, 0) + +}) + +cli::test_that_cli("Draft episodes are and added episodes ignored", { + + reset_episodes(res) + suppressMessages(set_episodes(res, get_episodes(res)[1], write = TRUE)) + expect_snapshot(drf <- get_drafts(res, "episodes")) + expect_equal(fs::path_file(drf), "02-new.Rmd", ignore_attr = TRUE) + +}) + + +cli::test_that_cli("No draft episodes reports all episodes published", { + + reset_episodes(res) + suppressMessages(set_episodes(res, get_episodes(res), write = TRUE)) + expect_snapshot(drf <- get_drafts(res, "episodes")) + expect_length(drf, 0) + +}) + + From 4594430bd1a7740301000648acae5776ec2b974e Mon Sep 17 00:00:00 2001 From: "Zhian N. Kamvar" Date: Tue, 27 Jul 2021 11:30:22 -0700 Subject: [PATCH 08/10] bump version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index c96d4e2a9..c135bc76d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: sandpaper Title: Create and Curate Carpentries Lessons -Version: 0.0.0.9032 +Version: 0.0.0.9033 Authors@R: c( person(given = "Zhian N.", family = "Kamvar", From 8a43330df88aa952bbf7d260e45458c3c4206c02 Mon Sep 17 00:00:00 2001 From: "Zhian N. Kamvar" Date: Tue, 27 Jul 2021 11:49:03 -0700 Subject: [PATCH 09/10] export get_drafts; fix smol mistakes --- NAMESPACE | 1 + R/build_markdown.R | 2 +- R/get_drafts.R | 1 + R/utils-paths-source.R | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 4c8231e59..cc15bf421 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -9,6 +9,7 @@ export(create_episode) export(create_lesson) export(fetch_github_workflows) export(get_config) +export(get_drafts) export(get_dropdown) export(get_episodes) export(get_instructors) diff --git a/R/build_markdown.R b/R/build_markdown.R index 7a1f6cb94..9b7267f0e 100644 --- a/R/build_markdown.R +++ b/R/build_markdown.R @@ -22,7 +22,7 @@ build_markdown <- function(path = ".", rebuild = FALSE, quiet = FALSE) { outdir <- path_built(path) # Determine build status for the episodes ------------------------------------ - source_list <- get_resource_list(path, warn = TRUE) + source_list <- get_resource_list(path, warn = !quiet) sources <- unlist(source_list, use.names = FALSE) names(sources) <- get_slug(sources) diff --git a/R/get_drafts.R b/R/get_drafts.R index 8f4c9ceb2..616352a87 100644 --- a/R/get_drafts.R +++ b/R/get_drafts.R @@ -14,6 +14,7 @@ #' to `NULL`, which indicates all folders listed in `config.yaml`. #' @param message if `TRUE` (default), an informative message about the files #' that are in draft status are printed to the screen. +#' @export #' #' @return a vector of paths to files in draft and a message (if specified) get_drafts <- function(path, folder = NULL, message = getOption("sandpaper.show_draft", TRUE)) { diff --git a/R/utils-paths-source.R b/R/utils-paths-source.R index 177b27151..91e9d976f 100644 --- a/R/utils-paths-source.R +++ b/R/utils-paths-source.R @@ -111,7 +111,7 @@ get_resource_list <- function(path, trim = FALSE, subfolder = NULL, warn = FALSE # These are the only four items that we need to consider order for. for (i in subfolder) { # If the configuration is not missing, then we have to rearrange the order. - res[[i]] <- parse_file_matches(res[[i]], cfg[[i]], warn = warn, subfolder) + res[[i]] <- parse_file_matches(res[[i]], cfg[[i]], warn = warn, i) } if (use_subfolder) res[[subfolder]] else res[names(res) != "site"] } From 0b25576fbbce0324ea3e720bca1e9b51d48f918b Mon Sep 17 00:00:00 2001 From: "Zhian N. Kamvar" Date: Tue, 27 Jul 2021 11:53:06 -0700 Subject: [PATCH 10/10] fix documentation --- R/build_lesson.R | 2 +- man/build_lesson.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/build_lesson.R b/R/build_lesson.R index 645f8458c..8740b1e12 100644 --- a/R/build_lesson.R +++ b/R/build_lesson.R @@ -24,7 +24,7 @@ #' create_lesson(tmp, open = FALSE) #' create_episode("first-script", path = tmp) #' check_lesson(tmp) -#' if (rmarkdown::pandoc_available()) +#' if (rmarkdown::pandoc_available("2.11")) #' build_lesson(tmp) build_lesson <- function(path = ".", rebuild = FALSE, quiet = !interactive(), preview = TRUE, override = list()) { diff --git a/man/build_lesson.Rd b/man/build_lesson.Rd index 952c0bdf1..0049a2286 100644 --- a/man/build_lesson.Rd +++ b/man/build_lesson.Rd @@ -41,6 +41,6 @@ tmp <- tempfile() create_lesson(tmp, open = FALSE) create_episode("first-script", path = tmp) check_lesson(tmp) -if (rmarkdown::pandoc_available()) +if (rmarkdown::pandoc_available("2.11")) build_lesson(tmp) }